我读了here:
make_shared(实际上)效率更高,因为它分配了 参考控制块与实际对象一起组成 动态分配。相比之下,shared_ptr的构造函数 采取裸对象指针必须分配另一个动态变量 作为参考计数
这是否意味着使用std :: make_shared创建的std :: shared_ptr的向量将是 “缓存友好”,因为数据(控制块和实际指针的数据)在一个块中?
我的用例是100 000个共享指针的向量,其中指向的对象是14个字节。
答案 0 :(得分:1)
使用make_shared
创建共享指针的向量是不可能的。试试吧,你不能这样做。您可以做的最好的事情是复制构造或复制从使用make_shared
的共享指针分配向量中的指针。但那时他们会在记忆中的其他地方。
但是,控制块仍将靠近对象。当你调用make_shared
时,你实际上做了三件事:一个对象,一个跟踪对象引用的共享指针控制块,以及一个共享指针。 make_shared
函数使控制块和对象本身在一个连续的内存块中分配。
这个缓存是否友好是一个有趣的问题。基本上,这取决于您如何使用该对象。
如果你经常只操作共享指针而不是它们指向的对象(例如,复制向量,从而增加每个共享指针上的引用计数),那么单独的分配可能会更加缓存友好,而不是make_share
给你的组合。
如果每次操作共享指针时经常对对象进行操作,那么make_shared
在典型情况下应该更加缓存。
答案 1 :(得分:1)
也许,但不要指望它。
对于缓存友好性,您希望尽可能少地使用内存,并且希望在地址中靠近的操作也能够及时靠近(也就是说,足够接近第二个操作使用仍然存在的内存)在第一次操作的某种程度的缓存中:缓存级别越低越好。
如果您使用make_shared
,则可能会略微节省总内存使用量,无论您使用何种内存使用模式,至少趋于将成为缓存的胜利
如果使用make_shared
,则控制块和引用的对象(referand)将在内存中相邻。
如果不使用make_shared
,并且您的对象与控制块的大小不同,那么使用通用内存分配器,对象将被合并在一起在一个地方,控制块在不同的地方聚集在一起。如果它们的是相同的大小(一旦被内存分配器以某种特定于实现的方式舍入),那么使用公共内存分配器就有可能它们只是在内存中交替进行长时间运行,除非{ {1}}会对此产生影响。
您的内存访问模式将确定哪些布局更适合缓存 - 当然,您在非shared_ptr
情况下获得的实际布局可能会再次出现,具体取决于实现细节。< / p>
您拥有make_shared
的事实基本上与所有这些无关,因为vector
对象与控制块和重新分类是分开的。
答案 2 :(得分:-1)
如上所述,使用make_shared创建一个对象会使“控制块”与所引用的对象相邻。
在你的情况下,我认为这是一个糟糕的选择。
当您分配内存时,即使在大块中,您也无法保证获得连续的“物理空间”,而不是稀疏,分散的页面分配。出于这个原因,遍历列表将导致跨越大内存的读取只是为了获得控制结构(然后指向数据)。
“但我的缓存行长64个字节!”你说。如果这是真的,你可能认为,“这将意味着对象与控制结构一起被加载到缓存中”,但这不一定是真的。这取决于许多因素,例如数据对齐,缓存行大小,缓存的关联性以及您使用的实际内存带宽。
您遇到的问题是首先需要获取控制结构以确定数据的位置,而不是可能已驻留在缓存中,因此部分数据(控制结构)可能如果你将它们全部分配在一起而不是make_shared,那么至少可以保证在缓存中。
如果要使数据缓存友好,您需要确保所有对它的引用都适合最高级别的缓存。继续使用它将有助于确保它保持在缓存中。缓存算法非常复杂,可以处理获取数据,除非您的代码非常分支。这是使您的数据“缓存友好:”在使用它时尽可能少使用分支的另一部分。
此外,在处理它时,请尝试将其分解为适合缓存的部分。如果可能的话,一次只能运行32k - 这是现代处理器上的保守数字。如果您确切知道将运行代码的CPU,那么如果需要,可以不那么保守地优化它。
编辑:我忘了提一个相关的细节。最常分配的页面大小为4k。缓存通常是“关联的”,尤其是在低端处理器中。双向关联意味着内存中的每个位置只能映射到每个其他缓存条目; 4向关联意味着它可以适用于4种可能的映射中的任何一种,8向意味着8种可能的映射中的任何一种。关联性越高,对您来说就越好。处理器上最快的缓存(L1)往往是最少关联的,因为它需要较少的控制逻辑;具有连续的数据块(例如连续的控制结构)是一件好事。完全关联缓存是可取的。