堆分配如何损害硬件缓存命中率
How can heap allocation hurt hardware cache hit ratio?
我做了一些测试来研究堆分配和硬件缓存行为之间的关系。经验结果具有启发性,但也可能具有误导性,尤其是在不同平台和复杂/不确定的用例之间。
我感兴趣的有两种情况:批量分配(实现自定义内存池)或后续分配(信任操作系统)。
以下是 C++ 中的两个示例分配测试
//Consequent allocations
for(auto i = 1000000000; i > 0; i--)
int *ptr = new int(0);
store_ptr_in_some_container(ptr);
//////////////////////////////////////
//Bulk allocation
int *ptr = new int[1000000000];
distribute_indices_to_owners(ptr, 1000000000);
我的问题是这些:
当我为只读操作迭代所有这些操作时,将如何缓存CPU 中的内存可能会自行分区吗?
尽管有经验结果(批量提高性能明显解决方案),当其他一些相对非常小的人时会发生什么批量分配是否覆盖以前分配的缓存?
将两者混合在一起以避免代码膨胀并保持代码可读性是否合理?
std::vector
、std::list
、std::map
、std::set
在这些概念中处于什么位置?
通用堆分配器有一组难以解决的问题。 它需要确保释放的内存可以回收,必须支持任意大小的分配,并强烈避免堆碎片。
这将始终包括每个分配的额外开销,即分配器所需的簿记。 它至少必须存储块的大小,以便在释放分配时可以正确回收它。 几乎总是指向堆段中下一个块的偏移量或指针,分配大小通常大于请求的大小,以避免碎片问题。
这种开销当然会影响缓存效率,当元素很小时,即使你从未使用它,你也会忍不住把它放进 L1 缓存。 当您在一个大口中分配数组时,每个数组元素的开销为零。 而且您有一个硬性保证,即每个元素在内存中都是相邻的,因此按顺序迭代数组的速度将与内存子系统支持的速度一样快。
通用分配器的情况并非如此,对于如此小的分配,开销可能是 100% 到 200%。 并且当程序运行了一段时间并且重新分配了数组元素时,也不能保证顺序访问。 值得注意的是,您的大数组无法支持的操作,因此请注意,不要自动假设分配长时间无法释放的巨型数组一定更好。
所以是的,在这种人为的场景中,你很可能会领先于大阵列。
Scratch std::list 从引用的集合类列表中,它的缓存效率非常差,因为下一个元素通常位于内存中完全随机的位置。 std::vector 是最好的,只是一个引擎盖下的数组。 std::map 通常使用红黑树完成,尽可能合理地完成,但您使用的访问模式当然很重要。与 std::set 相同。
- cmake更新缓存的变量
- 试图对缓存进行跨步测试,但程序并没有结束
- 缓存std::数组的选定元素,并在c++中自动保持其一致性
- 通过ccmake在cmake中缓存依赖选项
- 使用宏扩展的泛型:为什么指令缓存使用不当?
- 如何使缓存线程安全
- 存储指令是否会阻止缓存未命中的后续指令?
- 缓存局部性与函数调用
- Qt 网页程序集缓存
- 多线程减慢程序速度:无错误共享,无互斥锁,无缓存未命中,无小工作量
- std::shared_ptr vs std::make_shared:意外的缓存未命中和分支预测
- 多个 rocksdb 实例:使用单个共享缓存还是多个独立缓存?
- 无法链接 LRU 缓存C++
- 空函数的参数是否加载到缓存中?
- 是否可以检查变量是否位于 L1/L2/L3 缓存中
- dynamic_cast每次调用是否比具有空检查的缓存变量更昂贵?
- C++:如何在从给定缓存中排除数字的同时生成随机数
- 堆分配如何损害硬件缓存命中率
- 关联集缓存低估命中率
- 如何想出一个高缓存未命中率的例子