堆分配如何影响硬件缓存命中率?

时间:2013-10-30 13:49:58

标签: c++ caching heap

我已经做了一些测试来研究堆分配和硬件缓存行为之间的关系。实证结果具有启发性,但也可能具有误导性,尤其是在不同平台和复杂/不确定用例之间。

我感兴趣的有两种情况:批量分配(实现自定义内存池)或后续分配(信任操作系统)。

以下是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::vectorstd::liststd::mapstd::set在哪些方面有这些概念?

1 个答案:

答案 0 :(得分:1)

通用堆分配器有一系列难以解决的问题。它需要确保释放的内存可以回收,必须支持任意大小的分配,并强烈避免堆碎片。

这将始终包括每个分配的额外开销,分配器需要的簿记。它至少必须存储块的大小,以便在释放分配时可以正确地回收它。并且几乎总是偏移或指向堆段中下一个块的指针,分配大小通常大于请求以避免碎片问题。

这个开销当然会影响缓存效率,当元素很小时,你无法帮助它进入L1缓存,即使你从不使用它。当您在一个大吞吐量中分配数组时,每个数组元素都有开销。并且您有一个很难保证每个元素在内存中相邻,因此顺序迭代数组将与内存子系统可以支持的速度一样快。

通用分配器的情况并非如此,如此非常小的分配,开销可能是100到200%。当程序运行一段时间并重新分配数组元素时,无法保证顺序访问。值得一提的是你的大阵列不能支持的操作,所以要小心你不要自动假设分配长时间无法释放的巨型阵列必然会更好。

所以是的,在这种人为的情况下你很可能会领先大阵。

从引用的集合类列表中划分std :: list,它的缓存效率非常低,因为下一个元素通常位于内存中完全随机的位置。 std :: vector是最好的,只是引擎盖下的数组。 std :: map通常使用红黑树完成,尽管可以合理地完成,但您使用的访问模式当然很重要。同样适用于std :: set。