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

Kan*_*Liu 9 c++ performance computer-architecture cpu-cache

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

  1. 分配一个大阵列
  2. 每次访问不同大小的部分.

所以我写了一个小程序.这是我的代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

然而,结果是如此奇怪:

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
Run Code Online (Sandbox Code Playgroud)

我的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
Run Code Online (Sandbox Code Playgroud)

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

Lee*_*eor 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(*4个字节= 64B,一个缓存行 - 流检测器的经典单位步长)来延长步骤,j*64(4个缓存行).原因是 - 预取器可以为每个流请求发出2个预取,因此当你执行单元步长时它会先跑到你的代码之前,当你的代码跳过2行时可能仍然领先于你,但是当你的代码超过2行时,它可能会变得无用跳跃(3因为你的modulu不好,你需要一个step_size的分隔符)

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


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

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
Run Code Online (Sandbox Code Playgroud)

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

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

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