访问三个静态数组比一个包含3x数据的静态数组更快?

时间:2014-04-18 21:10:50

标签: c++ arrays performance optimization cpu

我有700个项目,每个循环700个项目我获得项目'三个属性并执行一些基本计算。我使用两种技术实现了这一点:

1)三个700元素数组,三个属性中的每一个都有一个数组。所以:

 item0.a = array1[0]
 item0.b = array2[0]
 item0.e = array3[0]

2)一个2100个元素的数组,包含三个属性的数据。所以:

 item0.a = array[(0*3)+0]
 item0.b = array[(0*3)+1]
 item0.e = array[(0*3)+2]

现在,三个项目属性abe在循环中一起使用 - 因此,如果将它们存储在一个数组中,性能应该更好是有意义的比如果使用三阵列技术(由于空间局部性)。但是:

  • 整个循环平均有三个700元素阵列= 3300个CPU周期
  • 整个循环平均一个2100个元素阵列= 3500个CPU周期

以下是2100阵列技术的代码:

unsigned int x;
unsigned int y;
double c = 0;
double d = 0;
bool data_for_all_items = true;
unsigned long long start = 0;
unsigned long long finish = 0;
unsigned int array[2100];

//I have left out code for simplicity. You can assume by now the array is populated.

start = __rdtscp(&x);

for(int i=0; i < 700; i++){
    unsigned short j = i * 3;
    unsigned int a = array[j + 0];     
    unsigned int b = array[j + 1];     

    data_for_all_items = data_for_all_items & (a!= -1 & b != -1);

    unsigned int e = array[j + 2];    

    c += (a * e);
    d += (b * e);
}

finish = __rdtscp(&y);

这里是三个700元素数组技术的代码:

unsigned int x;
unsigned int y;
double c = 0;
double d = 0;
bool data_for_all_items = true;
unsigned long long start = 0;
unsigned long long finish = 0;
unsigned int array1[700];
unsigned int array2[700];
unsigned int array3[700];

//I have left out code for simplicity. You can assume by now the arrays are populated.

start = __rdtscp(&x);

for(int i=0; i < 700; i++){
    unsigned int a= array1[i];     //Array 1
    unsigned int b= array2[i];     //Array 2

    data_for_all_items = data_for_all_items & (a!= -1 & b != -1);

    unsigned int e = array3[i];    //Array 3

    c += (a * e);
    d += (b * e);
}

finish = __rdtscp(&y);

为什么使用one-2100元素阵列的技术更快?这应该是因为每700个项目一起使用这三个属性。

我使用了MSVC 2012,Win 7 64

3x 700元素阵列技术的组装:

            start = __rdtscp(&x);
 rdtscp  
 shl         rdx,20h  
 lea         r8,[this]  
 or          rax,rdx  
 mov         dword ptr [r8],ecx  
 mov         r8d,8ch  
 mov         r9,rax  
 lea         rdx,[rbx+0Ch]  

            for(int i=0; i < 700; i++){
 sub         rdi,rbx  
                unsigned int a = array1[i];
                unsigned int b = array2[i];

                data_for_all_items = data_for_all_items & (a != -1 & b != -1);
 cmp         dword ptr [rdi+rdx-0Ch],0FFFFFFFFh  
 lea         rdx,[rdx+14h]  
 setne       cl  
 cmp         dword ptr [rdi+rdx-1Ch],0FFFFFFFFh  
 setne       al  
 and         cl,al  
 cmp         dword ptr [rdi+rdx-18h],0FFFFFFFFh  
 setne       al  
 and         cl,al  
 cmp         dword ptr [rdi+rdx-10h],0FFFFFFFFh  
 setne       al  
 and         cl,al  
 cmp         dword ptr [rdi+rdx-14h],0FFFFFFFFh  
 setne       al  
 and         cl,al  
 cmp         dword ptr [rdx-20h],0FFFFFFFFh  
 setne       al  
 and         cl,al  
 cmp         dword ptr [rdx-1Ch],0FFFFFFFFh  
 setne       al  
 and         cl,al  
 cmp         dword ptr [rdx-18h],0FFFFFFFFh  
 setne       al  
 and         cl,al  
 cmp         dword ptr [rdx-10h],0FFFFFFFFh  
 setne       al  
 and         cl,al  
 cmp         dword ptr [rdx-14h],0FFFFFFFFh  
 setne       al  
 and         cl,al  
 and         r15b,cl  
 dec         r8  
 jne         013F26DA53h  

                unsigned int e = array3[i];

                c += (a * e);
                d += (b * e);
            }


            finish = __rdtscp(&y);
 rdtscp  
 shl         rdx,20h  
 lea         r8,[y]  
 or          rax,rdx  
 mov         dword ptr [r8],ecx 

2100元素阵列技术的汇编程序:

            start = __rdtscp(&x);
 rdtscp  
 lea         r8,[this]  
 shl         rdx,20h  
 or          rax,rdx  
 mov         dword ptr [r8],ecx  

            for(int i=0; i < 700; i++){
 xor         r8d,r8d  
 mov         r10,rax  
                unsigned short j = i*3;
 movzx       ecx,r8w  
 add         cx,cx  
 lea         edx,[rcx+r8]  
                unsigned int a = array[j + 0];
                unsigned int b = array[j + 1];

                data_for_all_items = data_for_all_items & (best_ask != -1 & best_bid != -1);
 movzx       ecx,dx  
 cmp         dword ptr [r9+rcx*4+4],0FFFFFFFFh  
 setne       dl  
 cmp         dword ptr [r9+rcx*4],0FFFFFFFFh  
 setne       al  
 inc         r8d  
 and         dl,al  
 and         r14b,dl  
 cmp         r8d,2BCh  
 jl          013F05DA10h  

                unsigned int e = array[pos + 2];

                c += (a * e);
                d += (b * e);
            }


            finish = __rdtscp(&y);
 rdtscp  
 shl         rdx,20h  
 lea         r8,[y]  
 or          rax,rdx  
 mov         dword ptr [r8],ecx  

4 个答案:

答案 0 :(得分:2)

编辑:给定汇编代码,第二个循环展开五次。展开的版本可以在乱序执行CPU(例如任何现代x86 / x86-64 CPU)上运行得更快。


第二个代码是vectorisable - 每个数组的两个元素可以在每个迭代中加载一个XMM寄存器。由于现代CPU使用SSE进行标量和向量FP算法,因此将周期数减少一半左右。使用支持AVX的CPU,可以在YMM寄存器中加载四个双精度数,因此循环次数应该减少四个。

第一个循环不能沿着i进行向量化,因为迭代ai+1的值来自a之一的值之后的位置3个元素迭代i来自。在这种情况下,矢量化需要收集的矢量载荷,这些载荷仅在AVX2指令集中受支持。

在使用矢量功能编程CPU时,使用正确的数据结构至关重要。将第一个循环之类的代码转换为类似第二个循环的代码,是为了在英特尔至强拥有良好性能的情况下完成90%的工作,因为英特尔至强拥有非常宽的向量寄存器,但执行引擎速度非常慢。

答案 1 :(得分:2)

简单的答案是版本1是SIMD友好版本而版本2不是。但是,可以制作版本2,2100元素阵列,SIMD友好。你需要我们一个Hybrid Struct of Arrays,也就是阵列结构数组(AoSoA)。你安排这样的阵列:aaaa bbbb eeee aaaa bbbb eeee ....

下面是使用GCC的向量扩展来执行此操作的代码。请注意,现在2100元素数组代码看起来与700元素数组代码几乎相同,但它使用一个数组而不是三个数组。而不是在b和e之间有700个元素,它们之间只有12个元素。

我没有找到一个简单的解决方案,使用GCC向量扩展将uint4转换为double4,我现在不想花时间编写内在函数来执行此操作,因此我创建了c和{{1 unsigned int但是为了性能,我不想在循环中将uint4转换为double 4。

v

答案 2 :(得分:1)

“空间位置”的概念让你有点失望。有可能使用两个解决方案,您的处理器正在尽最大努力缓存阵列。

不幸的是,使用一个数组的代码版本也有一些正在执行的额外数学运算。这可能是您花费额外周期的地方。

答案 3 :(得分:1)

空间局部性确实很有用,但实际上它可以帮助你处理第二种情况(3个不同的阵列)。

缓存行大小为64字节(请注意,它不会在3中划分),因此对4或8字节值的单次访问有效地预取了下一个元素。另外,请记住,CPU HW预取程序可能会继续提前预取更多元素。

然而,当a,b,e被打包在一起时,你“浪费”了对同一迭代元素的这种有价值的预取。当你访问a时,预取b和e没有意义 - 下一次加载已经在那里(并且可能只是在CPU中与第一次加载合并或等待它检索数据)。实际上,当数组合并时 - 每64 /(3 * 4)= ~5.3次迭代只获取一次新内存行。错误的对齐甚至意味着在某些迭代中你会在你获得e之前有一个或多个b,这种不平衡通常是坏消息。

实际上,由于迭代是独立的,因为循环展开(如果已完成)和乱序执行(计算索引)的组合,您的CPU将继续并相对快速地开始第二次迭代下一组迭代很简单,并且不依赖于最后一个迭代发送的负载。但是,为了每次都发出下一个加载,你必须向前跑很远,并且最终有限大小的CPU指令队列会阻塞你,可能在达到完全潜在的内存带宽(并行未完成的负载数)之前。

另一方面,备选选项中有3个不同的数组,仅在迭代中使用空间局部性/ HW预取。在每次迭代中,您将发出3次加载,每64/4 = 16次迭代将获取一次完整行。获取的整体数据是相同的(好吧,它是相同的数据),但是时间性要好得多,因为你提前接下来的16次迭代而不是5次。当涉及HW预取因为你有3时,差异变得更大流而不是一个,意味着你可以发出更多的预取(并且看得更远)。