pytorch-notes

pytorch-notes

channles_last 内存格式

https://juejin.cn/post/7155302838636118047

一般是NCHW 格式内存排列,当数据为 (10x3x16x16) 时,对应的 stride 为 (768, 256, 16, 1),不同颜色的通道是分开的,一个颜色对应着一张图 alt text

而 channels_last 是 NHWC 格式内存排列,对应的 stride 为 (768, 1, 48, 3),channels last张量以通道为最密集维度的方式排序(也就是按像素存储图像),不同颜色的像素紧挨着 alt text

intrusive_ptr

侵入式指针,与std::shared_ptr的的区别: - 使用方式:侵入式表示需要访问指向对象的成员,intrusive_ptr 中的 T 必须继承自 intrusive_ptr_target,因为intrusive_ptr_target 提供了引用引用计数 refcount_,intrusive_ptr 作为 intrusive_ptr_target的友元可以访问refcount_,这也是侵入式的含义 - 使用场景:极致性能场景; 与需要this 指针的场景(std::enable_shared_from_this已经解决)

std::shared_ptr 的性能问题:因为引用计数需要在不同的 shared_ptr 对象之间共享,所以引用计数分配在堆上,其在构造时如果用new 的方式则需要两次堆内存分配,并且因为引用计数和数据对象的内存不在一起,因此会有缓存失效导致的性能问题。不过 std::make_shared 已解决了shared_ptr 的性能问题,其将引用计数内存和数据内存在一次内存申请中完成分配,但是新的问题是如果有weak_ptr,即使 shared_ptr 的作用域已经结束,其数据对象的内存也不会被释放,需要等着所有的 shared_ptr 和 weak_ptr 析构后,数据对象的内存才会释放。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// make_shared + weak_ptr 会使内存无法及时被释放
#include <iostream>
#include <memory>

class LargeObject {
public:
// 假设这里有一个很大的数据结构
char data[1024 * 1024]; // 1MB 的数据

~LargeObject() {
std::cout << "LargeObject destructor called" << std::endl;
}
};

int main() {
// 使用 std::make_shared 创建一个 LargeObject 的 shared_ptr
auto sharedPtr = std::make_shared<LargeObject>();

// 创建一个 weak_ptr 指向同一个 LargeObject
std::weak_ptr<LargeObject> weakPtr = sharedPtr;

// 销毁 shared_ptr,此时 LargeObject 的内存和控制块仍然不会被释放
// 因为 weakPtr 仍然指向它
sharedPtr.reset();

// 在这里,LargeObject 的内存和控制块仍然被保留
// 除非 weakPtr 也被销毁或过期

// 假设我们现在进行了一些其他操作...
// ...

// 当 weakPtr 离开作用域或被显式重置时,LargeObject 的析构函数才会被调用
// 并且其内存和控制块才会被释放
std::cout << "before weak_ptr" << std::endl;
exit(1);
// 为了演示,我们可以将 weakPtr 显式地设置为空
weakPtr.reset();

// 现在 LargeObject 的内存被释放了

return 0;
}
// 使用valgrind 检查内存,结果显示 make_shared<LargeObject> 的内存没有被释放
// valgrind --tool=memcheck --trace-children=yes --leak-check=full --show-leak-kinds=all --keep-debuginfo=yes ./a.out
/*
==333562==
==333562== HEAP SUMMARY:
==333562== in use at exit: 1,048,592 bytes in 1 blocks
==333562== total heap usage: 3 allocs, 2 frees, 1,122,320 bytes allocated
==333562==
==333562== 1,048,592 bytes in 1 blocks are still reachable in loss record 1 of 1
==333562== at 0x484B8F8: operator new(unsigned long) (vg_replace_malloc.c:483)
==333562== by 0x10A063: __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<LargeObject, std::allocator<LargeObject>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (in /mnt/data0/guoqing.feng/3d/output/a.out)
==333562== by 0x109EC3: std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<LargeObject, std::allocator<LargeObject>, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_ptr_inplace<LargeObject, std::allocator<LargeObject>, (__gnu_cxx::_Lock_policy)2> >&, unsigned long) (in /mnt/data0/guoqing.feng/3d/output/a.out)
==333562== by 0x109C9B: std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<LargeObject, std::allocator<LargeObject>, (__gnu_cxx::_Lock_policy)2> > > std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<LargeObject, std::allocator<LargeObject>, (__gnu_cxx::_Lock_policy)2> > >(std::allocator<std::_Sp_counted_ptr_inplace<LargeObject, std::allocator<LargeObject>, (__gnu_cxx::_Lock_policy)2> >&) (in /mnt/data0/guoqing.feng/3d/output/a.out)
==333562== by 0x109B53: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<LargeObject, std::allocator<LargeObject>>(LargeObject*&, std::_Sp_alloc_shared_tag<std::allocator<LargeObject> >) (in /mnt/data0/guoqing.feng/3d/output/a.out)
==333562== by 0x109A77: std::__shared_ptr<LargeObject, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<LargeObject>>(std::_Sp_alloc_shared_tag<std::allocator<LargeObject> >) (in /mnt/data0/guoqing.feng/3d/output/a.out)
==333562== by 0x1098C3: std::shared_ptr<LargeObject>::shared_ptr<std::allocator<LargeObject>>(std::_Sp_alloc_shared_tag<std::allocator<LargeObject> >) (in /mnt/data0/guoqing.feng/3d/output/a.out)
==333562== by 0x1096D3: std::shared_ptr<LargeObject> std::allocate_shared<LargeObject, std::allocator<LargeObject>>(std::allocator<LargeObject> const&) (in /mnt/data0/guoqing.feng/3d/output/a.out)
==333562== by 0x109527: std::shared_ptr<LargeObject> std::make_shared<LargeObject>() (in /mnt/data0/guoqing.feng/3d/output/a.out)
==333562== by 0x109203: main (in /mnt/data0/guoqing.feng/3d/output/a.out)
==333562==
==333562== LEAK SUMMARY:
==333562== definitely lost: 0 bytes in 0 blocks
==333562== indirectly lost: 0 bytes in 0 blocks
==333562== possibly lost: 0 bytes in 0 blocks
==333562== still reachable: 1,048,592 bytes in 1 blocks
==333562== suppressed: 0 bytes in 0 blocks
==333562==
==333562== For lists of detected and suppressed errors, rerun with: -s
==333562== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
*/

pytorch 中的 intusive_ptr 需要传递两个模板参数,

1
2
3
4
template <
class TTarget,
class NullType = detail::intrusive_target_default_null_type<TTarget>>
class intrusive_ptr;
NullType 类型必须有一个返回值是 TTarget* 的 singleton() 方法。为什么不直接用 nullptr呢?

官方在IValue 的定义中有提到,这样做是想通过让Tensor和c10::intrusive_ptr路径生成相同的代码以优化析构和相关操作的性能。下面是AI 的回答:

为什么使用UndefinedTensorImpl::singleton():

传统的空指针(nullptr)表示一个指针不指向任何有效的对象。然而,在处理c10::intrusive_ptr时,由于它不拥有对象,仅仅使用nullptr可能不足以提供足够的上下文或优化机会。 UndefinedTensorImpl::singleton()是一个特殊的TensorImpl实例,它表示一个未定义或无效的Tensor。通过使用这个单例而不是nullptr,c10::intrusive_ptr可以保持其“侵入式”的特性(即依赖对象内部的计数器),同时提供一个明确的、非空的表示空状态的方式。 这种表示法可能允许编译器或运行时优化,因为所有空的c10::intrusive_ptr都会指向同一个对象(UndefinedTensorImpl::singleton()),这可能会减少或简化某些检查或逻辑。 性能优化:

通过让Tensor和c10::intrusive_ptr路径生成相同的代码,可以更容易地利用现代编译器的优化技术,如内联展开、循环展开等。 当两种表示法(Tensor和c10::intrusive_ptr)在底层以相似的方式处理空状态时,它们可以共享更多的优化策略,从而提高整体性能。

在所有与 Tensor 相关的 intrusive_ptr 中,其 NullType 都是 UndefinedTensorImpl,其继承了TensorImpl,会构造出一个标记为无法使用的 TensorImpl,TensorImpl(DispatchKey::Undefined, caffe2::TypeMeta(),std::nullopt) ,并且会对 has_storage、is_contiguous_custom 等函数进行重载,因此在Tensor 为空的情况下,依然可以调用Tensor 相关的操作,这是nullptr 所无法实现的。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!