当前位置:  开发笔记 > 编程语言 > 正文

刷新缓存以防止基准测试流程

如何解决《刷新缓存以防止基准测试流程》经验,为你挑选了2个好方法。

我正在运行某人的c ++代码来对数据集进行基准测试.我遇到的问题是,我经常得到第一次运行的时间,如果我再次运行相同的代码,这些数字会大幅改变(即28秒到10秒).我认为这是因为CPU的自动缓存.有没有办法刷新缓存,或以某种方式防止这些波动?



1> Mats Peterss..:

没有一个"适用于所有事物,无处不在".大多数处理器都有特殊指令来刷新缓存,但它们通常是特权指令,因此必须从OS内核内部完成,而不是用户模式代码.当然,对于每个处理器架构,它都是完全不同的指令.

所有当前的x86处理器都有一条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();
}

使用rand将比填充常量/已知的东西慢得多.但编译器无法优化调用,这意味着它(几乎)保证代码将保留.

上面不会刷新指令缓存 - 这样做要困难得多,基本上,你必须运行一些(足够大的)其他代码才能可靠地执行.然而,指令缓存往往对整体基准性能的影响较小(指令缓存对于现代处理器的性能非常重要,这不是我所说的,但从某种意义上说,基准测试的代码通常足够小以至于它都适合在缓存中,基准测试在相同的代码上运行多次,所以它在第一次迭代时只会慢一些)

其他想法

模拟"非缓存"行为的另一种方法是为每个基准测试传递分配一个新区域 - 换句话说,在基准测试结束之前不释放内存或使用包含数据的数组,并输出结果,这样每次运行拥有自己的一组数据.

此外,实际测量基准测试的"热运行"的性能是常见的,而不是缓存为空的第一个"冷运行".这当然取决于你实际想要实现的目标......



2> BeeOnRope..:

这是我的基本方法:

    如果您可以动态确定LLC的大小(或者静态知道),则分配2倍于LLC大小的内存区域,否则,请分配感兴趣的平台1上最大LLC大小的合理倍数。

    memset将内存区域设置为一些非零值:1会很好。

    将指针“沉没”在某个位置,以使编译器无法优化上方或下方的内容(volatile几乎100%的时间写入全局内容)。

    从该区域中的随机索引读取,直到您平均触摸了每条缓存行10次左右(将读取的值累加为一个总和,以与(3)相似的方式吸收)。

这里有一些注释,说明为什么这通常行得通,为什么少做可能行不通-细节是以x86为中心的,但是类似的问题也适用于许多其他体系结构。

在开始主只读刷新循环之前,您绝对要写入已分配的内存(第2步),因为否则您可能只是从OS返回的相同的零映射小页面重复读取以满足您的内存分配。

您希望使用一个比LLC大小大得多的区域,因为外部高速缓存级别通常是物理寻址的,但是您只能分配和访问虚拟地址。如果仅分配一个LLC大小的区域,通常将无法完全了解每个缓存集的所有方式:某些集合将被过度代表(因此将被完全刷新),而其他集合将被代表不足因此,并非所有现有值都可以通过访问此内存区域来刷新。2倍的过度分配使得几乎所有集合都有足够的表示形式。

您要避免优化器做聪明的事情,例如注意内存永远不会逸出函数,并消除所有读取和写入操作。

您想在内存区域周围随机地进行迭代,而不是线性地遍历它:一些设计(例如最近的Intel上的LLC)会检测到何时出现“流”模式,并从LRU切换到MRU,因为LRU大概是最糟糕的情况此类负载的替换政策。这样做的结果是,无论通过内存流式传输多少次,您所做的工作之前的一些“旧”行都会保留在缓存中。随机访问内存会破坏此行为。

您想要访问的不仅仅是LLC内存量,原因如下:(a)分配超出LLC大小的原因(虚拟访问vs物理缓存),以及(b)因为随机访问需要更多访问,然后才有可能访问每一次设置足够的时间(c)高速缓存通常只是伪LRU,因此您需要的数目比完全LRU下要刷新每一行所需的访问次数还要多。

即使这不是万无一失的。上面未考虑的其他硬件优化或缓存行为可能导致此方法失败。您可能会对操作系统提供的页面分配感到非常不走运,而无法访问所有页面(您可以使用2MB的页面来缓解这种情况)。我强烈建议测试刷新技术是否足够:一种方法是在运行基准测试时使用CPU性能计数器来测量高速缓存未命中的数量,并根据已知的工作集大小2来查看该数量是否有意义。

请注意,这使高速缓存的所有级别都处于E(独占)状态或S(共享)状态,而不是M(已修改)状态。这意味着当用基准测试中的访问替换这些行时,无需将它们移到其他缓存级别:可以将其删除。在另一个答案中描述的方法将使大多数/所有行都保持在M状态,因此对于基准测试中访问的每一行,您最初将有1条驱逐流量。您可以通过将步骤4更改为写入而不是读取来实现与我的上述食谱相同的行为。

在这方面,这两种方法在本质上都不比另一种“更好”:在现实世界中,缓存级别将包含修改和未修改的行,而这些方法将缓存保留在连续统的两个极端。原则上,您可以同时对全M和无M状态进行基准测试,并查看它是否很重要:如果确实如此,则可以尝试评估高速缓存的真实状态通常将是什么副本。


1请记住,LLC的大小几乎每代CPU都在增长(主要是因为内核数在增加),因此,如果需要适应未来的发展,则需要留出一定的增长空间。

2我只是把它扔在那里,好像它很“简单”,但实际上取决于您的确切问题可能会非常困难。

推荐阅读
N个小灰流_701
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有