【问题标题】:Write a program to get CPU cache sizes and levels编写一个程序来获取 CPU 缓存大小和级别
【发布时间】:2013-10-08 19:58:42
【问题描述】:

我想编写一个程序来获取我的缓存大小(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;
}

然而,结果太诡异了:

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 Cache:32K(用于数据),L2 Cache 256K,L3 Cache 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。还是很奇怪。

【问题讨论】:

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


    【解决方案1】:

    我不确定这是否是这里唯一的问题,但这绝对是最大的问题 - 您的代码会很快触发硬件流预取器,使您几乎总是遇到 L1 或 L2 延迟。

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

    对于您的基准测试您应该禁用它们(通过 BIOS 或任何其他方式),或者至少通过替换 j*16(* 4 bytes per int = 64B,一个 cache line - 一个经典的单位步幅流检测器),带有j*64(4 个缓存行)。原因是 - 预取器可以为每个流请求发出 2 次预取,因此当您执行单元步幅时它会在您的代码之前运行,当您的代码跳过 2 行时可能仍然会领先您一点,但随着时间的推移变得基本无用跳跃(3 不好,因为你的模数,你需要一个 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 又被打破了。

    这使我们能够发现下一个错误 - 仅当它是 2 的幂时,与大小掩码进行与运算才有意义,否则您不会回绕,而是再次重复一些最后的地址(最终得到乐观的结果因为它在缓存中是新鲜的)。

    尝试将 ... &amp; size_mask 替换为 % steps[i]/sizeof(int),模数更昂贵,但如果您想拥有这些大小,则需要它(或者,当超过当前大小时,运行索引会归零)

    【讨论】:

    • 谢谢!我改用 j * 64,但结果仍然令人困惑。请查看我的更新。
    【解决方案2】:

    我认为您最好查看CPUID 指令。这不是小事,但网上应该有资料。

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

    【讨论】:

    • 这是个好方法。但我真的很想详细了解缓存是如何工作的,以及为什么我的代码会这样。还是谢谢。
    • @KanLiu - 你见过this article吗?它包含所有细节,包括缓存的东西。
    【解决方案3】:

    如果您使用的是 GNU/Linux,您可以阅读文件 /proc/cpuinfo 的内容并了解更多详细信息 /sys/devices/system/cpu/* 。在 UNIX 下,通常不定义 API,因为普通文件无论如何都可以完成这项工作。

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

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

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

    【讨论】:

    • 谢谢。正如我所说,缓存的大小和级别与我的代码行为不匹配,我想知道原因。
    猜你喜欢
    • 2012-09-17
    • 2010-10-22
    • 1970-01-01
    • 1970-01-01
    • 2018-09-12
    • 2011-02-28
    • 2021-11-27
    • 2018-02-05
    • 1970-01-01
    相关资源
    最近更新 更多