编写程序以获取CPU缓存大小和级别

时间:2013-10-02 12:25:44

标签: c++ performance computer-architecture cpu-cache

我想写一个程序来获取我的缓存大小(L1,L2,L3)。我知道它的一般想法。

  1. 分配一个大阵列
  2. 每次访问不同大小的部分。
  3. 所以我写了一个小程序。 这是我的代码:

    #include <cstdio>
    #include <time.h>
    #include <sys/mman.h>
    
    const int KB = 1024;
    const int MB = 1024 * KB;
    const int data_size = 32 * MB;
    const int repeats = 64 * MB;
    const int steps = 8 * MB;
    const int times = 8;
    
    long long clock_time() {
        struct timespec tp;
        clock_gettime(CLOCK_REALTIME, &tp);
        return (long long)(tp.tv_nsec + (long long)tp.tv_sec * 1000000000ll);
    }
    
    int main() {
        // allocate memory and lock
        void* map = mmap(NULL, (size_t)data_size, PROT_READ | PROT_WRITE, 
                         MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
        if (map == MAP_FAILED) {
            return 0;
        }
        int* data = (int*)map;
    
        // write all to avoid paging on demand
        for (int i = 0;i< data_size / sizeof(int);i++) {
            data[i]++;
        }
    
        int steps[] = { 1*KB, 4*KB, 8*KB, 16*KB, 24 * KB, 32*KB, 64*KB, 128*KB, 
                        128*KB*2, 128*KB*3, 512*KB, 1 * MB, 2 * MB, 3 * MB, 4 * MB, 
                        5 * MB, 6 * MB, 7 * MB, 8 * MB, 9 * MB};
        for (int i = 0; i <= sizeof(steps) / sizeof(int) - 1; i++) {
            double totalTime = 0;    
            for (int k = 0; k < times; k++) {
                int size_mask = steps[i] / sizeof(int) - 1;
                long long start = clock_time();
                for (int j = 0; j < repeats; j++) {
                    ++data[ (j * 16) & size_mask ];
                }
                long long end = clock_time();
                totalTime += (end - start) / 1000000000.0;
            }
            printf("%d time: %lf\n", steps[i] / KB, totalTime);
        }
        munmap(map, (size_t)data_size);
        return 0;
    }
    

    然而,结果很奇怪:

    1 time: 1.989998
    4 time: 1.992945
    8 time: 1.997071
    16 time: 1.993442
    24 time: 1.994212
    32 time: 2.002103
    64 time: 1.959601
    128 time: 1.957994
    256 time: 1.975517
    384 time: 1.975143
    512 time: 2.209696
    1024 time: 2.437783
    2048 time: 7.006168
    3072 time: 5.306975
    4096 time: 5.943510
    5120 time: 2.396078
    6144 time: 4.404022
    7168 time: 4.900366
    8192 time: 8.998624
    9216 time: 6.574195
    

    我的CPU是Intel(R)Core(TM)i3-2350M。 L1缓存:32K(用于数据),L2缓存256K,L3缓存3072K。 好像它没有遵循任何规则。我无法从中获取缓存大小或缓存级别的信息。 有人可以帮忙吗?提前谢谢。

    更新 关注@Leeor建议,我使用j*64代替j*16。新结果:

    1 time: 1.996282
    4 time: 2.002579
    8 time: 2.002240
    16 time: 1.993198
    24 time: 1.995733
    32 time: 2.000463
    64 time: 1.968637
    128 time: 1.956138
    256 time: 1.978266
    384 time: 1.991912
    512 time: 2.192371
    1024 time: 2.262387
    2048 time: 3.019435
    3072 time: 2.359423
    4096 time: 5.874426
    5120 time: 2.324901
    6144 time: 4.135550
    7168 time: 3.851972
    8192 time: 7.417762
    9216 time: 2.272929
    10240 time: 3.441985
    11264 time: 3.094753
    

    两个峰,4096K和8192K。仍然很奇怪。

3 个答案:

答案 0 :(得分:6)

我不确定这是否是唯一的问题,但它肯定是最大的问题 - 您的代码会很快触发HW流预取程序,使您几乎总是遇到L1或L2延迟。

更多细节可以在这里找到 - http://software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using-hardware-implemented-prefetchers

对于您的基准测试您应该禁用它们(通过BIOS或任何其他方式),或者至少通过替换j*16(*每个int 4个字节= 64B,一个缓存行 - 一个经典单元)来延长步数使用j*64(4个缓存行)来寻找流检测器。原因是 - 预取器可以为每个流请求发出2个预取,因此当你执行单元步长时,它会在你的代码之前运行,当你的代码跳过2行时可能仍然领先于你,但是在更长时间内变得无用跳跃(3因为你的modulu不好,你需要一个step_size的分隔符)

使用新结果更新问题,我们可以确定这里是否有其他内容。


EDIT1 : 好的,我运行了固定代码并得到了 -

1 time: 1.321001
4 time: 1.321998
8 time: 1.336288
16 time: 1.324994
24 time: 1.319742
32 time: 1.330685
64 time: 1.536644
128 time: 1.536933
256 time: 1.669329
384 time: 1.592145
512 time: 2.036315
1024 time: 2.214269
2048 time: 2.407584
3072 time: 2.259108
4096 time: 2.584872
5120 time: 2.203696
6144 time: 2.335194
7168 time: 2.322517
8192 time: 5.554941
9216 time: 2.230817

如果你忽略几列你会更有意义 - 你跳过32k(L1大小),但不是在256k(L2大小)之后跳跃,我们得到384的结果太好了,只跳到512K。最后一次跳跃是8M(我的LLC大小),但9k再次被打破。

这允许我们发现下一个错误 - 只有大小掩码的ANDing才有意义,当它是2的幂时,否则你不会回绕,而是重复一些最后的地址(最终会产生乐观的结果)因为它在缓存中很新鲜。)

尝试将... & size_mask替换为% steps[i]/sizeof(int),modulu更贵,但如果你想拥有这些尺寸,你需要它(或者,只要它超过当前尺寸就会变为零的运行索引)

答案 1 :(得分:4)

我认为你最好看一下CPUID指令。这不是微不足道的,但网上应该有信息。

此外,如果您使用的是Windows,则可以使用GetLogicalProcessorInformation功能。请注意,它仅出现在Windows XP SP3及更高版本中。我对Linux / Unix一无所知。

答案 2 :(得分:2)

如果您正在使用GNU / Linux,您只需阅读 / proc / cpuinfo 文件的内容,并了解更多详情 / sys / devices / system / cpu / { {1}} 的。在UNIX下常见的不是定义API,普通文件无论如何都可以完成这项工作。

我还要看一下 util-linux 的来源,它包含一个名为 lscpu 的程序。这应该是一个如何检索所需信息的例子。

//更新
http://git.kernel.org/cgit/utils/util-linux/util-linux.git/tree/sys-utils/lscpu.c

如果只是看看他们的来源。它基本上是从上面提到的文件中读取的,就是这样。因此,从文件中读取它们绝对有效,它们由内核提供。