C 内存的微型基准测试

C 内存的微型基准测试,c,benchmarking,microbenchmark,C,Benchmarking,Microbenchmark,我正试图用C语言编写一个微型基准测试来测试内存 我相信我的机器(英特尔i5)上的缓存大小是8MB 有人能提出一些逻辑来测试内存,同时确保缓存未命中率为100%吗 array1 = malloc(DCACHE_SIZE); array2 = malloc(DCACHE_SIZE); while(condition) memcpy(&array1[index], &array2[index], sizeof(char)); index++; 目前,使用memcpy,

我正试图用C语言编写一个微型基准测试来测试内存

我相信我的机器(英特尔i5)上的缓存大小是8MB

有人能提出一些逻辑来测试内存,同时确保缓存未命中率为100%吗

array1 = malloc(DCACHE_SIZE);
array2 = malloc(DCACHE_SIZE);
while(condition)
    memcpy(&array1[index], &array2[index], sizeof(char));
    index++;
目前,使用memcpy,我的程序每秒会对memcpy进行420782149次调用。。 我认为这个号码有严重的问题(它的缓存命中率很高)


如何避免缓存?

我不太熟悉英特尔i5缓存体系结构,但有两种基本方法适用于大多数处理器:

  • 禁用内存缓冲区的一级/二级/三级缓存。这可能是确保不使用缓存的唯一正确方法。这种方法的一种变体是将其他一些未使用内存区域的内容锁定到缓存中(即,如果不选择禁用)
  • 如果第一种方法不是一种选择,那么将数组设置为比DCACHE大小大得多,并在该区域上设置
    memcpy()
    。这里的想法是,缓存将被使用,但随着大型阵列的新部分被拉入缓存,缓存将被刷新。这将提供一个非常接近从CPU到内存的基准测试。如果您使用
    memset()
    而不是
    memcpy()
    ,并且您的缓存是直写的,那么这个基准应该与直接的CPU到内存路径相同
  • 在这两种情况下,为了获得更精确的结果,您应该确保
    array1[]
    array2[]
    的内容在开始测试之前不在缓存中。这可能需要在
    memcpy()测试之前分配和填充(或简单地读取)第三个缓冲区。在尝试避免缓存时,会遇到许多此类问题,如何解决和避免这些问题取决于缓存体系结构以及操作系统如何配置缓存(即,如果是Linux,默认情况下可能不会将缓存配置为直写)


    顺便说一句,您知道您正在使用
    memcpy()
    方法测试内存读写吗?这种方法很好,但可能会产生更不可靠的结果。一种更好的方法可能是单独测试读写操作,而不用像
    memset()
    memcpy()
    此外,在循环中至少运行测试10次,并记录结果。
    销毁并重新创建for循环中的阵列,并查看如果您只是在for循环之前分配阵列,时间会有多大差异

    在你的420M成绩上: 复制(读写)速度约为420 MB/s。取决于您的RAM速度,这似乎是一个较低的数字。

    你也可以查看一下弗吉尼亚大学的流基准来比较。

    < p>禁用上述缓存是相当复杂的,相反,你可以使用完全避免它们的数据操作方法。

    最好的方法是定义一个不可缓存的内存区域,这样每次读/写都会立即进入内存并跳过缓存填充,但这也需要在更高级的级别上调整程序


    我能想到的最简单的解决方案是直接使用跳过缓存的流式/非时态指令-如果编译器能够识别它们,请尝试使用_mm_stream_si64/_mm_stream_si32 intrinsic,或者在内联汇编部分直接使用movnt*assmebly指令族-它应该对处理器产生几乎相同的效果。请注意,它们操作的元素比单个字节大,因此您可能需要稍微重新排列代码

    强制缓存未命中的一种简单方法是在保证位于不同缓存窗口的区域之间跳跃,如:

    #include <string.h>
    #define DCACHE_SIZE (1024*1024*8)
    
    void dummy(){
    char *array1, *array2;
    size_t index, count;
    
    array1 = malloc(5*DCACHE_SIZE);
    array2 = malloc(5*DCACHE_SIZE);
    for(index=0,count=54321;count--; index = (index+3) % (5*DCACHE_SIZE)) {
        memcpy(&array1[index], &array2[index], 1);
        }
    }
    
    #包括
    #定义DCACHE_大小(1024*1024*8)
    虚设(){
    字符*array1,*array2;
    大小索引、计数;
    阵列1=malloc(5*DCACHE_尺寸);
    array2=malloc(5*DCACHE\u尺寸);
    对于(索引=0,计数=54321;计数--;索引=(索引+3)%(5*DCACHE_大小)){
    memcpy(&array1[index],&array2[index],1);
    }
    }
    

    上面的
    3
    5
    是任意选择的(但应该是相对最优的)
    1和
    2也足以在每次迭代时跳出缓存。还请注意,memcpy()的源和目标也位于不同的缓存插槽中,因此,如果缓存插槽少于2个,则此代码还会在循环的每次迭代中导致两次缓存未命中。顺便说一句:在我的机器上,GCC用内联指令替换memcpy()调用。

    也许你需要一个可变变量或缓冲区。也许你需要在
    memcpy(&array1[index],&array2[index],sizeof(char))周围添加
    {}
    ;索引++
    和/或初始化
    i
    ,或使用for()循环。顺便说一句:
    sizeof(char)
    根据定义是1。感谢您的详细回答。我一定会试试这个。而且,是的,我确实意识到我在测试读写。我试着两人一组做,我想一个memcpy既可以读,也可以写;因此,在写入array2时清除array1。由于array1和array2的大小为DCACHE\u size。不幸的是,这并不是那么简单。从array1[0]读取实际上会将缓存线字节(例如32或64字节)拉入缓存。同样,写入array2[0]会将缓存线字节拉入缓存。最终,包含array2[0]的缓存线将被刷新/写回内存,而包含array1[0]的缓存线将被简单地替换到缓存中。测试缓存有很多可变因素,这就是为什么我提到禁用内存区域的缓存是真正实现所需基准的最佳且可能是唯一的方法。