这章主要包括TensorImpl
与StorageImpl
的代码细节
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下,处理器访问它自己的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。