我测试了memcpy()的速度,注意到速度在i * 4KB时急剧下降。结果如下:Y轴是速度(MB /秒),X轴是memcpy()的缓冲区大小,从1KB增加到2MB。图2和图3详细说明了1KB-150KB和1KB-32KB的部分。

环境:

处理器:Intel(R)Xeon(R)CPU E5620 @ 2.40GHz

操作系统:2.6.35-22-generiC#33-Ubuntu

GCC编译器标志:-O3 -msse4 -DINTEL_SSE4 -Wall -std = c99

我想它一定与缓存有关,但是在以下对缓存不友好的情况下我找不到原因:

  • Why is my program slow when looping over exactly 8192 elements?
  • Why is transposing a matrix of 512x512 much slower than transposing a matrix of 513x513?

  • 由于这两种情况的性能下降是由不友好的循环导致的,该循环将分散的字节读取到缓存中,浪费了缓存行的其余空间。

    这是我的代码:
    void memcpy_speed(unsigned long buf_size, unsigned long iters){
        struct timeval start,  end;
        unsigned char * pbuff_1;
        unsigned char * pbuff_2;
    
        pbuff_1 = malloc(buf_size);
        pbuff_2 = malloc(buf_size);
    
        gettimeofday(&start, NULL);
        for(int i = 0; i < iters; ++i){
            memcpy(pbuff_2, pbuff_1, buf_size);
        }
        gettimeofday(&end, NULL);
        printf("%5.3f\n", ((buf_size*iters)/(1.024*1.024))/((end.tv_sec - \
        start.tv_sec)*1000*1000+(end.tv_usec - start.tv_usec)));
        free(pbuff_1);
        free(pbuff_2);
    }
    

    更新

    考虑到@ usr,@ ChrisW和@Leeor的建议,我更精确地重新测试,下面的图显示了结果。缓冲区大小从26KB到38KB,我每隔64B(26KB,26KB + 64B,26KB + 128B,......,38KB)对其进行测试。每个测试在0.15秒内循环100,000次。有趣的是,下降不仅发生在4KB边界内,而且还出现在4 * i + 2 KB处,下降幅度小得多。

    聚苯乙烯

    @Leeor提供了一种填补空缺的方法,在pbuff_1pbuff_2之间添加了2KB的虚拟缓冲区。它有效,但是我不确定Leeor的解释。

    最佳答案

    内存通常以4k页组织(尽管也支持更大的内存)。您的程序看到的虚拟地址空间可能是连续的,但在物理内存中不一定是这种情况。维护虚拟地址到物理地址(在页面映射中)的映射的OS通常会尝试将物理页面也保持在一起,但这并不总是可能的,并且它们可能会破裂(尤其是长时间使用时,有时可能会交换它们) )。

    当您的内存流越过4k页面边界时,CPU需要停止并获取新的翻译-如果已经看到该页面,则该页面可能会缓存在TLB中,并且访问被优化为最快。是第一次访问(或者如果要保留的TLB的页面太多),CPU将不得不停止内存访问并开始遍历页面映射条目-这相对较长,实际上每个级别自己读取的内存(在虚拟机上甚至更长,因为每个级别在主机上可能需要完整的页面遍历)。

    您的memcpy函数可能还有另一个问题-首次分配内存时,操作系统只会将页面构建到页面 map 中,但由于内部优化而将它们标记为未访问和未修改。首次访问不仅可以调用页面漫游,而且还可以帮助告知OS该页面将被使用(并存储到目标缓冲区页面中),这将需要昂贵的过渡到某些OS处理程序。

    为了消除这种噪音,请一次分配缓冲区,重复执行几次副本,然后计算摊销时间。另一方面,这会给您“温暖”的性能(即在对缓存进行预热之后),以便您看到缓存大小反射(reflect)在图形上。如果要在不遭受分页等待时间的情况下获得“冷”效果,则可能要在迭代之间刷新缓存(请确保不要计时)

    编辑

    重新阅读该问题,您似乎正在进行正确的测量。我的解释的问题是,在4k*i之后应该显示出逐渐增加的趋势,因为在每次下降时,您都会再次支付罚款,但随后应该享受免费乘车服务,直到下一个4k。它没有解释为什么会有这样的“尖峰”,并且在它们之后速度恢复正常。

    我认为您面临的问题与问题中链接的关键步幅问题类似-当您的缓冲区大小是4k左右时,两个缓冲区将对齐缓存中的相同集合并相互碰撞。您的L1是32k,所以一开始似乎没有什么问题,但是假设数据L1有8种方式,实际上是对相同集合的4k环绕,并且您有2 * 4k块具有完全相同的对齐方式(假设分配是连续进行的),因此它们在相同集合上重叠。 LRU不能按您预期的那样正常工作就足够了,您将不断遇到冲突。

    为了检查这一点,我将尝试在pbuff_1和pbuff_2之间分配一个虚拟缓冲区,使其大2k,并希望它能打破对齐方式。

    编辑2:

    好吧,既然这行得通了,是时候详细说明了。假设您在0x1000-0x1fff0x2000-0x2fff范围内分配了两个4k数组。 L1中的设置0将包含0x1000和0x2000的行,设置1将包含0x1040和0x2040,依此类推。在这些大小下,您还没有遇到任何问题,它们可以共存而不会溢出缓存的关联性。但是,每次执行迭代时,您都会有一个负载和一个存储访问相同的集合-我猜测这可能会导致硬件冲突。更糟糕的是,您需要多次迭代才能复制一行,这意味着您拥挤了8个负载+ 8个存储(如果进行矢量化处理则更少,但仍然很多),所有这些都指向同一组不良数据,我很漂亮确保那里藏有很多碰撞。

    我还看到Intel optimization guide对此有特定的要求(请参见3.6.8.2):

    关于performance - 为什么memcpy()的速度每4KB急剧下降?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21038965/

    10-11 19:29