繁体   English   中英

如何使用 c++ 代码验证 CPU 缓存行大小?

[英]How to verify CPU cache line size with c++ code?

我从Igor 的博客中阅读了一篇文章。 文章说:

...今天的 CPU 不能逐字节访问 memory。 相反,它们以(通常)64 字节的块(称为高速缓存行)获取 memory。 当您读取特定的 memory 位置时,整个缓存行将从主 memory 提取到缓存中。 而且,从同一缓存行访问其他值很便宜!

文章还提供了c#代码来验证上述结论:

int[] arr = new int[64 * 1024 * 1024];

// Loop 1 (step = 1)
for (int i = 0; i < arr.Length; i++) arr[i] *= 3;

// Loop 2 (step = 16)
for (int i = 0; i < arr.Length; i += 16) arr[i] *= 3;

两个 for 循环的时间大致相同:在 Igor 的机器上分别为 80 和 78 ms,因此验证了缓存行机制。

然后我参考上面的想法实现了一个c++版本来验证缓存行大小如下:

#include "stdafx.h"
#include <iostream>
#include <chrono>
#include <math.h>
using namespace std::chrono;

const int total_buff_count = 16;
const int buff_size = 32 * 1024 * 1024;

int testCacheHit(int * pBuffer, int size, int step)
{
    int result = 0;
    for (int i = 0; i < size;) {
        result += pBuffer[i];
        i += step;
    }

    return result;
}

int main()
{
    int * pBuffer = new int[buff_size*total_buff_count];

    for (int i = 0; i < total_buff_count; ++i) {
        int step = (int)pow(2, i);

        auto start = std::chrono::system_clock::now();
        volatile int result = testCacheHit(pBuffer + buff_size*i, buff_size, step);
        auto end = std::chrono::system_clock::now();

        std::chrono::duration<double> elapsed_seconds = end - start;
        std::cout << "step: " << step << ", elapsed time: " << elapsed_seconds.count() * 1000 << "ms\n";
    }

    delete[] pBuffer;
}

但我的测试结果与 Igor 的文章完全不同。 如果步长为1,则时间成本约为114ms; 如果步长为 16,则时间成本约为 78ms。 测试应用是用release配置构建的,我的机器上有32GB的memory,CPU是intel Xeon E5 2420 v2 2.2G; 结果如下。 C++ 测试结果

有趣的发现是,当 step 为 2 和 step 为 2048 时,时间成本显着降低。我的问题是,如何解释我的测试中 step 为 2 和 step 为 2048 时的差距? 为什么我的结果与 Igor 的结果完全不同? 谢谢。

我自己对第一个问题的解释是,代码的时间成本包含两部分:一个是“内存读/写”,其中包含 memory 读/写时间成本,另一个是“其他成本”,其中包含 for 循环和计算成本。 如果 step 是 2,那么“内存读/写”成本几乎没有变化(因为缓存行),但是计算和循环成本减少了一半,所以我们看到了明显的差距。 而且我猜我的 CPU 上的缓存线是 4096 字节(1024 * 4 字节)而不是 64 字节,这就是为什么我们在步长为 2048 时出现另一个差距的原因。但这只是我的猜测。 感谢您的帮助,谢谢。

介于 1024 和 2048 之间

请注意,您使用的是未初始化的数组 这基本上意味着

int * pBuffer = new int[buff_size*total_buff_count];

不会导致您的程序实际要求任何物理 memory。 相反,只保留了一些虚拟地址空间。

然后,如果您首先触摸某个数组元素,则会触发页面错误,并且操作系统会将页面映射到物理 memory。 这是一个相对较慢的操作,可能会显着影响您的实验。 由于您系统上的页面大小可能为4 kB ,它可以容纳1024 个 4 字节整数 当您 go 执行2048 step时,实际上只有每秒一次的页面被访问,并且运行时间按比例下降。

您可以通过提前“触摸”memory 来避免这种机制的负面影响:

int * pBuffer = new int[buff_size*total_buff_count]{};

当我尝试这样做时,我在 64 到 8192 步长之间几乎线性减少了时间。

介于 1 和 2 之间

您系统上的缓存行大小绝对不是 2048 字节,它很可能是 64 字节(通常,它可能具有不同的值,甚至对于不同的缓存级别可能具有不同的值)。

至于第一部分,对于step为 1,只涉及更多的算术运算(数组元素的添加和i的增量)。

与伊戈尔实验的区别

我们只能推测为什么伊戈尔的实验在两种情况下给出的时间几乎相同。 我猜算术的运行时间在那里可以忽略不计,因为只涉及一个循环计数器增量并且他写入数组,这需要将缓存行额外传输回 memory (我们可以说字节/操作比率远高于您的实验。)

如何使用 c++ 代码验证 CPU 缓存行大小?

C++17中有std::hardware_destructive_interference_size破坏性_interference_size,它应该提供最小的缓存行大小。 请注意,它是一个编译时间值,编译器依赖于您对目标机器的输入。 当针对整个架构时,数字可能不准确。

如何使用 c++ 代码验证 CPU 缓存行大小?

你确实不能。

你应该编写可移植的 C++ 代码。 阅读n3337

假设您没有在 C++ 编译器中启用编译器优化 想象一下,你在一些模拟器(比如这些)中运行你的 C++ 编译器。

特别是在 Linux 上,您可以解析/proc/cpuinfo伪文件并从中获取 CPU 缓存行大小。

例如:

% head -20 /proc/cpuinfo
processor   : 0
vendor_id   : AuthenticAMD
cpu family  : 23
model       : 8
model name  : AMD Ryzen Threadripper 2970WX 24-Core Processor
stepping    : 2
microcode   : 0x800820b
cpu MHz     : 1776.031
cache size  : 512 KB
physical id : 0
siblings    : 48
core id     : 0
cpu cores   : 24
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 13
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid amd_dcm aperfmperf pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb hw_pstate sme ssbd sev ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 xsaves clzero irperf xsaveerptr arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif overflow_recov succor smca

顺便说一句,有许多不同的组织和缓存级别。

You could imagine a C++ application on Linux parsing the output of /proc/cpuinfo then making HTTP requests (using libcurl ) to the Web to get more from it.

另请参阅答案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM