32

为了清楚起见,完全重写/更新(和你的理智,它有点太长了)......旧帖子

对于分配,我需要找到每个缓存的级别(L1、L2、...)和大小。给出提示和我到目前为止发现的内容:我认为这个想法是创建不同大小的数组并读取它们。定时这些操作:

sizes = [1k, 4k, 256K, ...]
foreach size in sizes 
    create array of `size`

    start timer
    for i = 0 to n // just keep accessing array
        arr[(i * 16) % arr.length]++ // i * 16 supposed to modify every cache line ... see link
    record/print time

已更新(UTC+8 9 月 28 日下午 6:57)

另请参阅完整来源

好的,现在按照@mah 的建议,我可能已经修复了 SNR 比率问题......并且还找到了一种为我的代码计时的方法(wall_clock_time来自实验室示例代码)

但是,我似乎得到了不正确的结果:我在 Intel Core i3 2100 上:[规格]

  • L1:2×32K
  • L2:2×256K
  • 三级:3MB

我得到的结果,在图表中:

lengthMod:1KB 到 512K

在此处输入图像描述

第一个峰值的基数是32K ...合理...第二个是384K ...为什么?我期待256?

lengthMod:512k 到 4MB

在此处输入图像描述

那为什么这个范围会一团糟呢?


我还阅读了有关预取或来自其他应用程序的干扰,因此我在脚本运行时关闭了尽可能多的东西,它始终(通过多次运行)显示 1MB 及以上的数据总是如此混乱?

4

6 回答 6

36

经过 10 分钟搜索 Intel 说明手册和另外 10 分钟编码后,我想出了这个(对于基于 Intel 的处理器):

void i386_cpuid_caches () {
    int i;
    for (i = 0; i < 32; i++) {

        // Variables to hold the contents of the 4 i386 legacy registers
        uint32_t eax, ebx, ecx, edx; 

        eax = 4; // get cache info
        ecx = i; // cache id

        __asm__ (
            "cpuid" // call i386 cpuid instruction
            : "+a" (eax) // contains the cpuid command code, 4 for cache query
            , "=b" (ebx)
            , "+c" (ecx) // contains the cache id
            , "=d" (edx)
        ); // generates output in 4 registers eax, ebx, ecx and edx 

        // See the page 3-191 of the manual.
        int cache_type = eax & 0x1F; 

        if (cache_type == 0) // end of valid cache identifiers
            break;

        char * cache_type_string;
        switch (cache_type) {
            case 1: cache_type_string = "Data Cache"; break;
            case 2: cache_type_string = "Instruction Cache"; break;
            case 3: cache_type_string = "Unified Cache"; break;
            default: cache_type_string = "Unknown Type Cache"; break;
        }

        int cache_level = (eax >>= 5) & 0x7;

        int cache_is_self_initializing = (eax >>= 3) & 0x1; // does not need SW initialization
        int cache_is_fully_associative = (eax >>= 1) & 0x1;

        // See the page 3-192 of the manual.
        // ebx contains 3 integers of 10, 10 and 12 bits respectively
        unsigned int cache_sets = ecx + 1;
        unsigned int cache_coherency_line_size = (ebx & 0xFFF) + 1;
        unsigned int cache_physical_line_partitions = ((ebx >>= 12) & 0x3FF) + 1;
        unsigned int cache_ways_of_associativity = ((ebx >>= 10) & 0x3FF) + 1;

        // Total cache size is the product
        size_t cache_total_size = cache_ways_of_associativity * cache_physical_line_partitions * cache_coherency_line_size * cache_sets;

        printf(
            "Cache ID %d:\n"
            "- Level: %d\n"
            "- Type: %s\n"
            "- Sets: %d\n"
            "- System Coherency Line Size: %d bytes\n"
            "- Physical Line partitions: %d\n"
            "- Ways of associativity: %d\n"
            "- Total Size: %zu bytes (%zu kb)\n"
            "- Is fully associative: %s\n"
            "- Is Self Initializing: %s\n"
            "\n"
            , i
            , cache_level
            , cache_type_string
            , cache_sets
            , cache_coherency_line_size
            , cache_physical_line_partitions
            , cache_ways_of_associativity
            , cache_total_size, cache_total_size >> 10
            , cache_is_fully_associative ? "true" : "false"
            , cache_is_self_initializing ? "true" : "false"
        );
    }
}

参考: 英特尔® 64 和 IA-32 架构开发人员手册:卷。2A ,第 3-190 页,CPUID——CPU 标识。

这比测量缓存延迟要可靠得多,因为在现代处理器上关闭缓存预取几乎是不可能的。如果您需要不同处理器架构的类似信息,则必须查阅相应的手册。

于 2012-10-02T20:04:08.580 回答
13

测量你的时间所花费的时间(即调用clock() 函数的时间)比执行所花费的时间要大很多很多(很多很多......)arr[(i*16)&lengthMod]++。这种极低的信噪比(以及其他可能的缺陷)使您的计划无法实施。很大一部分问题是您试图测量循环的单次迭代;您链接的示例代码试图测量一组完整的迭代(在开​​始循环之前读取时钟;从循环中出现后再次读取;不要在循环内使用 printf() )。

如果您的环路足够大,您可能能够克服信噪比问题。

至于“正在增加什么元素”;arr是一个1MB缓冲区的地址;arr[(i * 16) & lengthMod]++;导致(i * 16) * lengthMod从该地址生成偏移量;该偏移量是增加的 int 的地址。您正在执行移位(i * 16 将变为 i << 4)、逻辑和、加法,然后是读/加/写或单个增量,具体取决于您的 CPU)。

编辑:如上所述,由于内存访问的相对速度(缓存或无缓存)和调用函数只是为了测量时间,您的代码的 SNR(信噪比)较差。为了获得您当前获得的时间,我假设您将代码修改为如下所示:

int main() {
    int steps = 64 * 1024 * 1024;
    int arr[1024 * 1024];
    int lengthMod = (1024 * 1024) - 1;
    int i;
    double timeTaken;
    clock_t start;

    start = clock();
    for (i = 0; i < steps; i++) {
        arr[(i * 16) & lengthMod]++;
    }
    timeTaken = (double)(clock() - start)/CLOCKS_PER_SEC;
    printf("Time for %d: %.12f \n", i, timeTaken);
}

这会将测量移到循环之外,因此您不是在测量单个访问(这实际上是不可能的),而是在测量steps访问。

您可以steps根据需要自由增加,这将直接影响您的时间安排。由于您收到的时间太接近了,在某些情况下甚至是倒置的(您的时间在大小之间振荡,这不太可能是由缓存引起的),您可以尝试将值更改为stepsto256 * 1024 * 1024甚至更大。

注意:您可以steps尽可能大地放入有符号的 int (应该足够大),因为逻辑并确保您在缓冲区中环绕。

于 2012-09-26T03:44:24.780 回答
3

我知道这个!(实际上由于预取,它非常复杂)

 for (times = 0; times < Max; time++) /* many times*/
     for (i=0; i < ArraySize; i = i + Stride)
           dummy = A[i]; /* touch an item in the array */

更改步幅允许您测试缓存的属性。通过查看图表,您将得到答案。

查看幻灯片 35-42 http://www.it.uu.se/edu/course/homepage/avdark/ht11/slides/11_Memory_and_optimization-1.pdf

Erik Hagersten 是一位非常优秀的老师(而且也非常称职,曾经是 sun 的首席架构师)所以请查看他的其他幻灯片以获得更多精彩的解释!

于 2012-09-26T04:38:12.907 回答
2

要回答您关于 1MB 以上的奇怪数字的问题,这很简单;缓存逐出策略与分支预测有关,以及 L3 缓存在内核之间共享的事实。

Core i3 有一个非常有趣的缓存结构。实际上任何现代处理器都可以。你应该在维基百科上阅读它们;它有各种各样的方法来决定“好吧,我可能不需要这个......”然后它可以说“我会把它放在受害者缓存中”或任何数量的东西。L1-2-3 缓存时序可能非常复杂,这取决于大量因素和做出的个别设计决策。

最重要的是,所有这些决定以及更多(参见关于该主题的维基百科文章)必须在两个核心的缓存之间同步。将共享的 L3 缓存与单独的 L1 和 L2 缓存同步的方法可能很难看,它们可能涉及回溯和重新计算或其他方法。您不太可能拥有完全免费的第二个内核,并且没有任何东西竞争 L3 缓存空间,从而导致同步异常。

通常,如果您正在处理数据,例如卷积内核,您希望确保它适合 L1 缓存并围绕它塑造您的算法。L3 缓存并不真正意味着以您的方式处理数据集(尽管它比主内存更好!)

我发誓,如果我是那个必须实现缓存算法的人,我会发疯的。

于 2012-10-05T03:12:06.123 回答
1

对于不同步幅的基准测试,您可以尝试 lmbench 包中的 lat_mem_rd,它是开源的:http: //www.bitmover.com/lmbench/lat_mem_rd.8.html

我已经将我的 Windows 端口发布到http://habrahabr.ru/post/111876/ ——在这里复制粘贴很长。那是两年前的事了,我没有用现代 CPU 测试它。

于 2012-10-05T11:08:41.017 回答
-1

对于 Windows,您可以使用GetLogicalProcessorInformation方法。

对于 linux,您可以使用sysconf(). 您可以找到sysconfin/usr/include/unistd.h或的有效参数/usr/include/bits/confname.h

于 2012-10-05T08:08:19.207 回答