C程序,用于确定缓存的级别和大小

Jie*_*eng 30 c caching

完全重写/更新为了清晰(和你的理智,它的长期太长)...(旧帖子)

对于赋值,我需要找到每个缓存的级别(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
Run Code Online (Sandbox Code Playgroud)

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

另见完整来源

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

但是,我似乎得到了不正确的结果:我在英特尔酷睿i3 2100上:[ SPECS ]

  • L1:2 x 32K
  • L2:2 x 256K
  • L3:3MB

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

lengthMod:1KB到512K

在此输入图像描述

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

lengthMod:512k到4MB

在此输入图像描述

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


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

Ser*_* L. 32

在搜索英特尔指令手册10分钟并进行另外10分钟编码后,我想出了这个(针对基于英特尔的处理器):

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 

        // taken from http://download.intel.com/products/processor/manual/325462.pdf Vol. 2A 3-149
        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;


        // taken from http://download.intel.com/products/processor/manual/325462.pdf 3-166 Vol. 2A
        // 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"
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

参考:http://download.intel.com/products/processor/manual/325462.pdf 3-166 Vol.2A

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

编辑:添加了缓存类型描述符.Edit2:添加了缓存级别指示器.Edit3:添加了更多文档.

  • 这是工程师的回答,喜欢它:).如果信息存在,为什么不直接查找 (4认同)

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

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

您可以steps根据需要自由增加,这将对您的时间产生直接影响.既然你收到的时间太长并拢,在某些情况下甚至反转(你的时间振荡的大小,这是不容易通过高速缓存之间引起的),你可能会尝试改变的值steps256 * 1024 * 1024甚至更大.

注意:您可以使用steps尽可能大的符号int(它应该足够大),因为逻辑并确保您在缓冲区中包装.