我有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]
现在,三个项目属性a
,b
和e
在循环中一起使用 - 因此,如果将它们存储在一个数组中,性能应该更好是有意义的比如果使用三阵列技术(由于空间局部性)。但是:
以下是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
答案 0 :(得分:2)
编辑:给定汇编代码,第二个循环展开五次。展开的版本可以在乱序执行CPU(例如任何现代x86 / x86-64 CPU)上运行得更快。
第二个代码是vectorisable - 每个数组的两个元素可以在每个迭代中加载一个XMM寄存器。由于现代CPU使用SSE进行标量和向量FP算法,因此将周期数减少一半左右。使用支持AVX的CPU,可以在YMM寄存器中加载四个双精度数,因此循环次数应该减少四个。
第一个循环不能沿着i
进行向量化,因为迭代a
中i+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时,差异变得更大流而不是一个,意味着你可以发出更多的预取(并且看得更远)。