刷新缓存以防止基准测试波动 我正在运行某人的C++代码来对数据集进行基准测试。我遇到的问题是,我经常会得到第一次运行的时间,如果我再次运行相同的代码,这些时间会发生很大的变化(即28秒到10秒)。我假设这是由于CPU的自动缓存造成的。有没有一种方法可以刷新缓存,或者以某种方式防止这些波动?
没有一种方法“适用于所有地方”。大多数处理器都有特殊指令来刷新缓存,但它们通常是特权指令,因此必须从操作系统内核内部完成,而不是从用户模式代码中完成。当然,每个处理器架构的指令完全不同 当前所有x86处理器都有一条刷新缓存以防止基准测试波动 我正在运行某人的C++代码来对数据集进行基准测试。我遇到的问题是,我经常会得到第一次运行的时间,如果我再次运行相同的代码,这些时间会发生很大的变化(即28秒到10秒)。我假设这是由于CPU的自动缓存造成的。有没有一种方法可以刷新缓存,或者以某种方式防止这些波动?,c++,caching,benchmarking,timing,C++,Caching,Benchmarking,Timing,没有一种方法“适用于所有地方”。大多数处理器都有特殊指令来刷新缓存,但它们通常是特权指令,因此必须从操作系统内核内部完成,而不是从用户模式代码中完成。当然,每个处理器架构的指令完全不同 当前所有x86处理器都有一条clflush指令,用于刷新一条缓存线,但要做到这一点,必须有要刷新的数据(或代码)的地址。这对于小而简单的数据结构来说是很好的,但是如果你有一棵到处都是的二叉树,这就不太好了。当然,根本不是便携式的 在大多数环境中,读取和写入大量替代数据,例如: // Global variable
clflush
指令,用于刷新一条缓存线,但要做到这一点,必须有要刷新的数据(或代码)的地址。这对于小而简单的数据结构来说是很好的,但是如果你有一棵到处都是的二叉树,这就不太好了。当然,根本不是便携式的
在大多数环境中,读取和写入大量替代数据,例如:
// Global variables.
const size_t bigger_than_cachesize = 10 * 1024 * 1024;
long *p = new long[bigger_than_cachesize];
...
// When you want to "flush" cache.
for(int i = 0; i < bigger_than_cachesize; i++)
{
p[i] = rand();
}
//全局变量。
常量大小大于缓存大小=10*1024*1024;
long*p=新长[大于缓存大小];
...
//当您想要“刷新”缓存时。
for(int i=0;i<大于缓存大小;i++)
{
p[i]=rand();
}
使用rand
比填充常量/已知值要慢得多。但是编译器无法优化调用,这意味着(几乎)可以保证代码将保持不变
上述方法不会刷新指令缓存——这要困难得多,基本上,您必须运行一些(足够大的)其他代码才能可靠地完成这项工作。然而,指令缓存对整体基准性能的影响往往较小(指令缓存对于现代处理器的性能非常重要,这不是我要说的,但从某种意义上说,基准测试的代码通常足够小,可以放入缓存中,并且基准测试在同一代码上运行多次,所以第一次迭代时速度较慢)
其他想法
模拟“非缓存”行为的另一种方法是为每个基准测试过程分配一个新区域——换句话说,在基准测试结束之前不释放内存,或者使用包含数据的数组并输出结果,这样每次运行都有自己的数据集来处理
此外,通常会实际测量基准测试的“热运行”性能,而不是缓存为空的第一次“冷运行”。这当然取决于您实际试图实现的目标…以下是我的基本方法:
memset
将内存区域设置为非零值:1
就可以了volatile
全局几乎100%的时间都有效)- 在开始主只读刷新循环之前,您绝对希望写入分配的内存(步骤2),因为否则您可能只是重复读取操作系统返回的同一个小内存,以满足内存分配
- 您希望使用比LLC大小大得多的区域,因为外部缓存级别通常是物理寻址的,但您只能分配和访问虚拟地址。如果您只分配LLC大小的区域,通常无法完全覆盖每个缓存集的所有方式:某些缓存集将被过度表示(因此将被完全刷新),而其他集合的表示不足,因此甚至不能通过访问该内存区域刷新所有现有值。2倍的过度分配使得几乎所有集合都有足够的表示
- 您希望避免优化器做一些聪明的事情,例如注意内存永远不会逃逸函数,并消除所有读写操作
- 您希望在内存区域周围随机迭代,而不仅仅是线性地跨越内存区域:一些设计,如最近Intel上的LLC,会在“流”时检测到模式存在,并且从LRU切换到MRU,因为LRU对于这种负载来说可能是最糟糕的替换策略。其效果是,无论您在内存中流多少次,在您的努力可以保留在缓存中之前的一些“旧”行。随机访问内存会破坏这种行为
- 您希望访问多于LLC的内存量,原因如下:(a)与分配多于LLC大小(虚拟访问与物理缓存)的原因相同,(b)因为随机访问需要更多的访问,然后才有可能命中每个集合足够的次数(c)缓存通常只是伪LRU,因此需要的访问次数超过在精确LRU下预期的访问次数才能清除每一行