Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/72.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
对cpu缓存基准测试结果感到困惑_C_Performance_Caching_Benchmarking_Cpu Cache - Fatal编程技术网

对cpu缓存基准测试结果感到困惑

对cpu缓存基准测试结果感到困惑,c,performance,caching,benchmarking,cpu-cache,C,Performance,Caching,Benchmarking,Cpu Cache,在Ulrich Drepper的“每个程序员都应该知道的内存”中,描述了一个简单的基准测试,演示了缓存对顺序读取的影响。基准测试包括遵循(顺序排列的)循环链表,并测量每个节点花费的平均时间。节点结构如下所示: struct l { struct l *n; long int pad[NPAD]; }; 大小为N的工作集包含N/sizeof(l)节点。有了足够的填充空间,每个指针都存储在不同的缓存线中,因此我们需要一个大小至少为64*N/sizeof(l)的缓存,以遵循循环,而不需要昂贵

在Ulrich Drepper的“每个程序员都应该知道的内存”中,描述了一个简单的基准测试,演示了缓存对顺序读取的影响。基准测试包括遵循(顺序排列的)循环链表,并测量每个节点花费的平均时间。节点结构如下所示:

struct l {
  struct l *n;
  long int pad[NPAD];
};
大小为
N
的工作集包含
N/sizeof(l)
节点。有了足够的填充空间,每个指针都存储在不同的缓存线中,因此我们需要一个大小至少为
64*N/sizeof(l)
的缓存,以遵循循环,而不需要昂贵的内存访问

让我困惑的是:假设
64*N/sizeof(l)
正好是最大缓存的大小。比如说,如果我们通过改变填充将
sizeof(l)
减半,
N
也需要小2倍,这样才能使缓存中的所有内容都适合。因此,在不同的步幅下,我们应该在不同的工作集大小下用完缓存。但是(我没有足够的rep来嵌入图像)文本似乎与此相矛盾-对于所有三种NPAD大小,访问时间的突然跳跃发生在
N=2^19

我错过了什么

起初,我认为可能填充也会被缓存,但这并不是答案中所解释的那样

有了足够的填充空间,每个指针都存储在不同的缓存线中,因此我们需要一个大小至少为
64*N/sizeof(l)
的缓存,以遵循循环,而不需要昂贵的内存访问

否,如果缓存线为64字节,则如果
sizeof(struct l)
至少为64字节,则每个节点位于不同的缓存线中。但这不是第3.3.2节的要点

假设
64*N/sizeof(l)
正好是最大缓存的大小。比如说,如果我们通过改变填充将sizeof(l)减半,那么如果我们想让所有内容都仍然适合缓存,N也需要小2倍

缓存的大小将是固定的字节数,而不是像
64*N/sizeof(l)
这样的计算。在您提到的本小节中,Drepper使用1 MiB(220字节)二级缓存。此外,Drepper使用N将“工作集大小”(显然是总数组大小,尽管在某些试验中并非所有数组都被访问)描述为2N字节,因此如果2N字节小于或等于缓存大小,“工作集”适合缓存。如果数组大小为2N字节,则元素数必须为2N/
sizeof(struct l)

在图3.11中,您看到的是:

  • 只要总数组大小小于219字节,其中的所有内容都可以放入二级缓存,并且访问“快速”
  • 在219字节和220字节时,整个数组本身可以放入缓存中,但系统中还有其他东西也需要缓存,因此我们看到了中间效果,我将在这里不详细讨论
  • 超过220个字节时,数组元素在连续访问时无法保留在缓存中(即使使用较大的填充,实际访问的字节总数远小于缓存大小,缓存关联性使得它们溢出其指定的缓存集,实际上与溢出所有缓存相同)。
    • 对于零填充,预取功能很好:硬件观察连续读取的内存并预取缓存线。请注意,使用零填充时,一条缓存线中可以容纳多个元素,因此一条缓存线的每次预取都会提供多个元素,因此每个元素的时间较短
    • 对于较小的填充(假设每个缓存线有一个元素),预取的操作方式相同:硬件观察连续读取的内存并预取缓存线。但是,每个缓存线只有一个数组元素,因此每个元素的时间更高,即使每个缓存线的时间相同
    • 对于中等填充和较大填充,预取要么不工作,要么效果不佳
我对德雷珀的一些说法表示怀疑。例如,关于15和31
long int
填充之间的差异(显然将元素大小从128字节更改为256字节),他写道:

元素大小阻碍预取工作的地方是硬件预取限制的结果:它不能跨越页面边界

然而,由于页面大小为4096字节(根据其他地方提到的60页为245760字节推断),元素间距为128到256字节,读取将分别每32和16个元素遇到页面边界。每个元素从大约145个周期跳到大约320个周期,因为每个元素有额外的1/32页转换(每16个元素1个,减去每32个元素1个,每个元素1个),这意味着1/32•C=320−145,其中C是页面转换成本的周期数。然后C=(320-145)•32=5600。如果没有更具针对性的测试或支持性的文档,我不会相信这个大数字

德雷珀还用括号中的斜体字写道:

是的,这有点不一致,因为在其他测试中,我们在元素大小中计算结构的未使用部分,并且我们可以定义NPAD,以便每个元素填充一个页面。在这种情况下,工作集的大小将非常不同。不过,这不是本测试的重点,因为预取是无效的,所以没有什么区别

这与他之前对工作集的描述不符,他说2N字节的工作集包含2N/
sizeof(struct l)
元素。后者显示他的“工作集”是整个数组,即使只访问每个元素的一部分。前者表示,工作集将根据每个元素的访问量而有所不同

总的来说,我认为德雷珀对原因的描述和归因有些松散。特别是如果没有示例代码,很难遵循一些测试描述或进行测试