这章主要包括TensorImplStorageImpl的代码细节

TensorImpl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct C10_API TensorImpl : public c10::intrusive_ptr_target 
protected:
  Storage storage_;  
  std::unique_ptr<c10::AutogradMetaInterface> autograd_meta_ = nullptr;  //只有is_variable的时候自动微分才生效
  c10::VariableVersion version_counter_;
  PyObject* pyobj_ = nullptr; // weak reference

  SmallVector<int64_t,5> sizes_;  //每个维度的size
  SmallVector<int64_t,5> strides_; //每个维度元素的间隔

  int64_t storage_offset_ = 0;  
  int64_t numel_ = 1;  //元素总个数
  caffe2::TypeMeta data_type_;
  c10::optional<c10::Device> device_opt_;

  TensorTypeId type_id_;
  bool is_contiguous_ = true;  //在内存中是否连续
  bool is_wrapped_number_ = false;

  bool allow_tensor_metadata_change_ = true; //拒绝 detach产出的tensor被更改
  bool reserved_ = false;
};

上面是tensorimpl只包括成员的定义,strides这里值得重点关注

  • 如果tensor在内存中存储连续,即is_contiguous=true, 以tensor[2][3][4]为例,第0维的strides就是 3*4=12,即strides数组为{12,4,1},相当于每个维数右边的size的积

  • 如果tensor在内存中不连续,a[3][6]narrow操作后变为a[3][4]

1
2
  Tensor a:  00 01 02 03 04 05 10 11 12 13 14 15 20 21 22 23 24 25 
  Tensor b:  00 01 02 03  x  x 10 11 12 13  x  x 20 21 22 23  x  x

则strides还是{6,1},需要考虑被narrow后仍占用的内存

所以当需要访问Tensor中具体的角标ijk内存地址时storageOffset + i * stride[0] + j * stride[1] + k * stride[2]

StorageImpl

1
2
3
4
5
6
7
8
struct C10_API StorageImpl final : public c10::intrusive_ptr_target {
 private:
  caffe2::TypeMeta data_type_;  // 数据类型
  DataPtr data_ptr_;            // 指向存储数据的内存块
  int64_t numel_;               // 数据总数
  bool resizable_;
  Allocator* allocator_;        // 内存分配器
}
  • data_ptr 其实是个UniquePtr,比较特殊的是这个unique_ptr是unique_ptr的void特化

Allocator

  • Allocator是个虚基类,继承时需要实现allocate和raw_deleter
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct C10_API Allocator {
  virtual ~Allocator() = default;

  virtual DataPtr allocate(size_t n) const = 0;

  //using DeleterFnPtr = void (*)(void*);
  virtual DeleterFnPtr raw_deleter() const { //这里需要返回一个安全的资源释放函数
    return nullptr;
  }
  void* raw_allocate(size_t n) {
    auto dptr = allocate(n);
    AT_ASSERT(dptr.get() == dptr.get_context());
    return dptr.release_context();
  }
  void raw_deallocate(void* ptr) {
    auto d = raw_deleter();
    AT_ASSERT(d);
    d(ptr);
  }
};

举个例子,cpuAllocator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct C10_API DefaultCPUAllocator final : at::Allocator {
  DefaultCPUAllocator() {}
  ~DefaultCPUAllocator() override {}
  at::DataPtr allocate(size_t nbytes) const override {  //virtual override
    void* data = alloc_cpu(nbytes);  //类外函数,为了兼容不同平台的alloc函数
    if (FLAGS_caffe2_report_cpu_memory_usage && nbytes > 0) {
      getMemoryAllocationReporter().New(data, nbytes); //使用unorderd_map 
      return {data, data, &ReportAndDelete, at::Device(at::DeviceType::CPU)};
    }
    return {data, data, &free_cpu, at::Device(at::DeviceType::CPU)};
  }

  static void ReportAndDelete(void* ptr) {
    if (!ptr) {
      return;
    }
    getMemoryAllocationReporter().Delete(ptr);
    free_cpu(ptr);
  }

  at::DeleterFnPtr raw_deleter() const override { //virtual override
    if (FLAGS_caffe2_report_cpu_memory_usage) {
      return &ReportAndDelete;  
    }
    return &free_cpu;
  }

 protected:
  static MemoryAllocationReporter& getMemoryAllocationReporter() {  //这个结构记录了所有已分配的指针与对应的size
    static MemoryAllocationReporter reporter_;  //静态,使用的时候加了互斥锁
    return reporter_;
  }
};

术语:

  • NUMA:非统一内存访问架构(英语:Non-uniform memory access)是一种为多处理器的计算机设计的内存架构,内存访问时间取决于内存相对于处理器的位置。在NUMA下,处理器访问它自己的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。