【问题标题】:Is there a way to simulate cache locality when benchmarking?基准测试时有没有办法模拟缓存局部性?
【发布时间】:2020-03-15 22:29:43
【问题描述】:

我试图找出对 C++ 程序进行基准测试的最佳方法,并希望模拟与基准测试部分相关的数据存在于缓存中和冷时两种情况。

是否有一种可靠的方法可以在 x86-64 机器上强制执行好的和坏的缓存局部性,作为测试运行的准备形式,假设它所涉及的数据是已知的?

【问题讨论】:

  • 您将在连续访问内存时获得良好的缓存命中率。如果您知道缓存大小,则可以在一个缓存行中访问尽可能多的元素。对于糟糕的缓存命中率,您可以只使用对内存的随机访问。这几乎肯定会每次都破坏缓存。还有很多事情需要考虑,但问题太宽泛,无法深入到每一个细节。
  • 在运行前让缓存变热很容易:首先进行热身运行。无论如何,您都应该这样做以避免很多不良影响。例如Idiomatic way of performance evaluation? 描述了许多陷阱。循环一个不同的大数组通常会从缓存中驱逐大多数其他数据。
  • 测试“缓存”场景的典型做法是运行一次测试函数并丢弃测量数据,然后开始实际测试。当然,这并不完美,因为函数本身可能会使自己的缓存无效。其次,要测试“冷”场景,请确保通过加载一堆数据来毒化缓存。这是极少数情况之一,您可能会发现 std::list 很有用。
  • 您尝试过 cachgrind 吗? valgrind.org/docs/manual/cg-manual.html

标签: c++ x86-64 benchmarking cpu-cache microbenchmark


【解决方案1】:

假设您正在对一种算法进行基准测试,该算法对一系列对象执行操作,并且您关心这些对象在内存中的位置(以及缓存)。

要“模拟”局部性:创建局部性。您可以创建局部性高的链表和局部性低的链表:

在数组中分配节点。要创建具有高局部性的列表,请确保数组的第一个元素指向第二个元素,依此类推。要创建局部性较低的列表,请创建顺序的随机排列,以便每个节点在数组的随机位置指向另一个节点。

确保元素的数量至少比最大缓存大一个数量级。

【讨论】:

  • 迭代 std::list 与 std::vector 不仅是连续的还是分散的;链表会在内存/缓存加载使用延迟上造成瓶颈(在主流 x86 CPU 上,L1d 命中通常需要 4 个周期)。但是对于数组/std::vector 中的元素在编译时已知是连续的,可以使用简单的指针增量来计算地址,从而创建内存级并行性。 (并允许自动矢量化)。这比恰好将其节点连续分配的链表要好得多。指针追踪循环中一个元素的缓存未命中会阻塞以后的迭代
  • @PeterCordes 好点。禁用矢量化优化可能会有所帮助。以及在编译时使数组的大小未知。
  • 这只会帮助很小的一部分。对于元素之间的指针追逐(mov rdi, [rdi])具有最小 4 周期的加载使用延迟,这比 add rdi, 4 指针增量要差得多,例如对容器的元素求和。它破坏了内存级别的并行性。 (尽管如果列表节点碰巧是连续的,硬件预取仍然可以工作)。另外不要忘记next 指针占用空间,因此list<int> 有16 字节元素,而vector<int> 有4 字节元素(在x86-64 等典型实现上)。
  • 即使您使用gcc -O3 -fno-tree-vectorize 进行编译,您也无法简单地放入列表与向量并获得对迭代容器结果的循环的意义。它引入了许多令人困惑的因素,您必须详细了解编译器、asm 和 CPU,您不妨对您关心的调优的真实循环进行一些性能分析!
  • @PeterCordes 我重写了答案,建议在两种情况下都使用相同的数据结构。
猜你喜欢
  • 2010-11-03
  • 1970-01-01
  • 2017-01-10
  • 2020-11-17
  • 2017-08-06
  • 1970-01-01
  • 1970-01-01
  • 2020-04-16
  • 2019-02-10
相关资源
最近更新 更多