在循环迭代之间消耗整个缓存线有什么特别的好处吗?

问题描述 投票:0回答:1

我的程序添加了浮点数组,并在通过 MSVC 和 G++ 进行最大优化编译时展开 4 倍。我不明白为什么两个编译器都选择展开 4x,所以我做了一些测试,发现只是偶尔在运行时手动展开 1-vs-2 或 1-vs-4 迭代的 t 测试给出 p 值 ~0.03, 2-vs-4 很少 < 0.05, and 2-vs-8+ was always > 0.05.

如果我将编译器设置为使用 128 位向量或 256 位向量,它总是展开 4x,这是 64 字节缓存行的倍数(有意义还是巧合?)。

我考虑缓存行的原因是因为我没想到展开会对顺序读取和写入千兆字节浮点数的内存绑定程序产生任何影响。在这种情况下展开是否有好处?也有可能没有显着差异,而且我的样本量不够大。

我发现这个博客说,对于中等大小的数组,手动展开数组副本速度更快,而对于较长的数组,流式传输速度最快。他们的 AvxAsyncPFCopier 和 AvxAsyncPFUnrollCopier 函数似乎受益于使用整个缓存行以及手动展开。博客中的基准测试来源此处

#include <iostream>
#include <immintrin.h>

int main() {
    // example of manually unrolling float arrays
    size_t bytes = sizeof(__m256) * 10;
    size_t alignment = sizeof(__m256);
    // 10 x 32-byte vectors
    __m256* a = (__m256*) _mm_malloc(bytes, alignment); 
    __m256* b = (__m256*) _mm_malloc(bytes, alignment);
    __m256* c = (__m256*) _mm_malloc(bytes, alignment); 

    for (int i = 0; i < 10; i += 2) {
        // cache miss?
        // load 2 x 64-byte cache lines:
        //      2 x 32-byte vectors from b
        //      2 x 32-byte vectors from c
        a[i + 0] = _mm256_add_ps(b[i + 0], c[i + 0]);

        // cache hit?
        a[i + 1] = _mm256_add_ps(b[i + 1], c[i + 1]);

        // special bonus for consuming whole cache lines?
    }
}

3 个独特浮点数组的原始来源

for (int64_t i = 0; i < size; ++i) {
    a[i] = b[i] + c[i];
}

带有 AVX2 指令的 MSVC

            a[i] = b[i] + c[i];
00007FF7E2522370  vmovups     ymm2,ymmword ptr [rax+rcx]  
00007FF7E2522375  vmovups     ymm1,ymmword ptr [rcx+rax-20h]  
00007FF7E252237B  vaddps      ymm1,ymm1,ymmword ptr [rax-20h]  
00007FF7E2522380  vmovups     ymmword ptr [rdx+rax-20h],ymm1  
00007FF7E2522386  vaddps      ymm1,ymm2,ymmword ptr [rax]  
00007FF7E252238A  vmovups     ymm2,ymmword ptr [rcx+rax+20h]  
00007FF7E2522390  vmovups     ymmword ptr [rdx+rax],ymm1  
00007FF7E2522395  vaddps      ymm1,ymm2,ymmword ptr [rax+20h]  
00007FF7E252239A  vmovups     ymm2,ymmword ptr [rcx+rax+40h]  
00007FF7E25223A0  vmovups     ymmword ptr [rdx+rax+20h],ymm1  
00007FF7E25223A6  vaddps      ymm1,ymm2,ymmword ptr [rax+40h]  
00007FF7E25223AB  add         r9,20h  
00007FF7E25223AF  vmovups     ymmword ptr [rdx+rax+40h],ymm1  
00007FF7E25223B5  lea         rax,[rax+80h]  
00007FF7E25223BC  cmp         r9,r10  
00007FF7E25223BF  jle         main$omp$2+0E0h (07FF7E2522370h) 

带有默认指令的MSVC

            a[i] = b[i] + c[i];
00007FF71ECB2372  movups      xmm0,xmmword ptr [rax-10h]  
00007FF71ECB2376  add         r9,10h  
00007FF71ECB237A  movups      xmm1,xmmword ptr [rcx+rax-10h]  
00007FF71ECB237F  movups      xmm2,xmmword ptr [rax+rcx]  
00007FF71ECB2383  addps       xmm1,xmm0  
00007FF71ECB2386  movups      xmm0,xmmword ptr [rax]  
00007FF71ECB2389  addps       xmm2,xmm0  
00007FF71ECB238C  movups      xmm0,xmmword ptr [rax+10h]  
00007FF71ECB2390  movups      xmmword ptr [rdx+rax-10h],xmm1  
00007FF71ECB2395  movups      xmm1,xmmword ptr [rcx+rax+10h]  
00007FF71ECB239A  movups      xmmword ptr [rdx+rax],xmm2  
00007FF71ECB239E  movups      xmm2,xmmword ptr [rcx+rax+20h]  
00007FF71ECB23A3  addps       xmm1,xmm0  
00007FF71ECB23A6  movups      xmm0,xmmword ptr [rax+20h]  
00007FF71ECB23AA  addps       xmm2,xmm0  
00007FF71ECB23AD  movups      xmmword ptr [rdx+rax+10h],xmm1  
00007FF71ECB23B2  movups      xmmword ptr [rdx+rax+20h],xmm2  
00007FF71ECB23B7  add         rax,40h  
00007FF71ECB23BB  cmp         r9,r10  
00007FF71ECB23BE  jle         main$omp$2+0D2h (07FF71ECB2372h)  
c++ visual-c++ cpu-architecture simd cpu-cache
1个回答
0
投票

我认为编译器展开循环的决定可能会受到多种因素的影响,包括指令流水线、指令级并行性和内存访问模式。展开循环有助于为编译器提供更多机会来优化指令调度并降低循环成本,从而有可能提高性能。

在您的情况下,由于您正在处理内存限制操作,因此主要瓶颈可能是内存访问而不是计算。展开循环可以通过增加内存预取机会和降低循环成本来帮助提高性能。

© www.soinside.com 2019 - 2024. All rights reserved.