我今天读到了有关数组索引和指针算法的知识,并且知道两个是相同的东西(例如,数组索引是指针算术的语法糖)。
除此之外,其他人说数组索引比指针算法快。 (??)
我想出了这两个代码来测试性能。我在C#中对它们进行了测试,但我认为它在C或C ++中是相同的。
for (int i = 0; i < n; i++) // n is size of an array
{
sum += arr1[i] * arr2[i];
}
for (int i = 0; i < n; i++) // n is size of an array
{
sum += *(arrP1 + i) * *(arrP2 + i);
}
我运行代码片1000次,数组有超过数百万的整数。结果对我来说意外。指针算法比数组索引略快(约%10)。
结果看起来是否正确?是否有任何解释可以理解?
我想添加生成的汇编代码。
sum += arr1[i] * arr2[i]; // C# Code
mov eax,dword ptr [ebp-14h]
cmp dword ptr [eax+4],0
ja 00E63195
call 73EDBE3A
mov eax,dword ptr [eax+8]
mov edx,dword ptr [ebp-0Ch]
cmp edx,dword ptr [eax+4]
jb 00E631A5
call 73EDBE3A
mov eax,dword ptr [eax+edx*4+8]
mov edx,dword ptr [ebp-18h]
cmp dword ptr [edx+4],0
ja 00E631B7
call 73EDBE3A
mov edx,dword ptr [edx+8]
mov ecx,dword ptr [ebp-0Ch]
cmp ecx,dword ptr [edx+4]
jb 00E631C7
call 73EDBE3A
imul eax,dword ptr [edx+ecx*4+8]
add dword ptr [ebp-8],eax
sum += *(arrP1 + i) * *(arrP2 + i); // C# Code
mov eax,dword ptr [ebp-10h]
mov dword ptr [ebp-28h],eax
mov eax,dword ptr [ebp-28h]
mov edx,dword ptr [ebp-1Ch]
mov eax,dword ptr [eax+edx*4]
mov edx,dword ptr [ebp-14h]
mov dword ptr [ebp-2Ch],edx
mov edx,dword ptr [ebp-2Ch]
mov ecx,dword ptr [ebp-1Ch]
imul eax,dword ptr [edx+ecx*4]
add eax,dword ptr [ebp-18h]
mov dword ptr [ebp-18h],eax
答案 0 :(得分:2)
基于手部优化指针的代码是:
int* arrP1 = arr1;
int* limitP1 = &arr1[n];
int* arrP2 = arr2;
for (; arrP1<limitP1; ++arrP1, ++arrP2)
{
sum += *arrP1 * *arrP2;
}
请注意,编译器优化代码可能比手动优化代码更好,手工优化可能会掩盖行为,阻止编译器优化,因此可能会使事情变得更糟。
因此,对于一个足够愚蠢的编译器优化器,我们可以预期手动优化指针版本比索引版本更好。但是你需要更多的专业知识(加上检查生成的代码和分析)才能在实际代码中有用地做这样的事情。
此外,如果读取任一输入数组导致缓存未命中,代码的整个优化(无论你是这样做还是编译器都这样做)最终都是完全没用的,因为任何近乎理智的版本都具有与其他任何近乎理智相同的性能匹配缓存未命中的版本。
答案 1 :(得分:1)
当然,在C ++中,结果是相同的[并且也将在C中]:
#include <stdio.h>
extern int arr1[], arr2[];
const int n = 1000000;
int main()
{
int sum = 0;
for (int i = 0; i < n; i++) // n is size of an array
{
sum += arr1[i] * arr2[i];
}
printf("sum=%d\n", sum);
int* arrP1 = arr1;
int* arrP2 = arr2;
for (int i = 0; i < n; i++) // n is size of an array
{
sum += *(arrP1 + i) * *(arrP2 + i);
}
printf("sum=%d\n", sum);
}
使用clang++ -O2 -S arrptr.cpp
进行编译为第一个循环提供了这个:
pxor %xmm0, %xmm0
movq $-4000000, %rax # imm = 0xFFFFFFFFFFC2F700
pxor %xmm1, %xmm1
.align 16, 0x90
.LBB0_1: # %vector.body
# =>This Inner Loop Header: Depth=1
movdqu arr1+4000000(%rax), %xmm3
movdqu arr1+4000016(%rax), %xmm4
movdqu arr2+4000000(%rax), %xmm2
movdqu arr2+4000016(%rax), %xmm5
pshufd $245, %xmm2, %xmm6 # xmm6 = xmm2[1,1,3,3]
pmuludq %xmm3, %xmm2
pshufd $232, %xmm2, %xmm2 # xmm2 = xmm2[0,2,2,3]
pshufd $245, %xmm3, %xmm3 # xmm3 = xmm3[1,1,3,3]
pmuludq %xmm6, %xmm3
pshufd $232, %xmm3, %xmm3 # xmm3 = xmm3[0,2,2,3]
punpckldq %xmm3, %xmm2 # xmm2 = xmm2[0],xmm3[0],xmm2[1],xmm3[1]
pshufd $245, %xmm5, %xmm6 # xmm6 = xmm5[1,1,3,3]
pmuludq %xmm4, %xmm5
pshufd $232, %xmm5, %xmm3 # xmm3 = xmm5[0,2,2,3]
pshufd $245, %xmm4, %xmm4 # xmm4 = xmm4[1,1,3,3]
pmuludq %xmm6, %xmm4
pshufd $232, %xmm4, %xmm4 # xmm4 = xmm4[0,2,2,3]
punpckldq %xmm4, %xmm3 # xmm3 = xmm3[0],xmm4[0],xmm3[1],xmm4[1]
paddd %xmm0, %xmm2
paddd %xmm1, %xmm3
movdqu arr1+4000032(%rax), %xmm1
movdqu arr1+4000048(%rax), %xmm4
movdqu arr2+4000032(%rax), %xmm0
movdqu arr2+4000048(%rax), %xmm5
pshufd $245, %xmm0, %xmm6 # xmm6 = xmm0[1,1,3,3]
pmuludq %xmm1, %xmm0
pshufd $232, %xmm0, %xmm0 # xmm0 = xmm0[0,2,2,3]
pshufd $245, %xmm1, %xmm1 # xmm1 = xmm1[1,1,3,3]
pmuludq %xmm6, %xmm1
pshufd $232, %xmm1, %xmm1 # xmm1 = xmm1[0,2,2,3]
punpckldq %xmm1, %xmm0 # xmm0 = xmm0[0],xmm1[0],xmm0[1],xmm1[1]
pshufd $245, %xmm5, %xmm6 # xmm6 = xmm5[1,1,3,3]
pmuludq %xmm4, %xmm5
pshufd $232, %xmm5, %xmm1 # xmm1 = xmm5[0,2,2,3]
pshufd $245, %xmm4, %xmm4 # xmm4 = xmm4[1,1,3,3]
pmuludq %xmm6, %xmm4
pshufd $232, %xmm4, %xmm4 # xmm4 = xmm4[0,2,2,3]
punpckldq %xmm4, %xmm1 # xmm1 = xmm1[0],xmm4[0],xmm1[1],xmm4[1]
paddd %xmm2, %xmm0
paddd %xmm3, %xmm1
addq $64, %rax
jne .LBB0_1
第二个循环确实:
pxor %xmm1, %xmm1
pxor %xmm0, %xmm0
movaps (%rsp), %xmm2 # 16-byte Reload
movss %xmm2, %xmm0 # xmm0 = xmm2[0],xmm0[1,2,3]
movq $-4000000, %rax # imm = 0xFFFFFFFFFFC2F700
.align 16, 0x90
.LBB0_3: # %vector.body45
# =>This Inner Loop Header: Depth=1
movdqu arr1+4000000(%rax), %xmm3
movdqu arr1+4000016(%rax), %xmm4
movdqu arr2+4000000(%rax), %xmm2
movdqu arr2+4000016(%rax), %xmm5
pshufd $245, %xmm2, %xmm6 # xmm6 = xmm2[1,1,3,3]
pmuludq %xmm3, %xmm2
pshufd $232, %xmm2, %xmm2 # xmm2 = xmm2[0,2,2,3]
pshufd $245, %xmm3, %xmm3 # xmm3 = xmm3[1,1,3,3]
pmuludq %xmm6, %xmm3
pshufd $232, %xmm3, %xmm3 # xmm3 = xmm3[0,2,2,3]
punpckldq %xmm3, %xmm2 # xmm2 = xmm2[0],xmm3[0],xmm2[1],xmm3[1]
pshufd $245, %xmm5, %xmm6 # xmm6 = xmm5[1,1,3,3]
pmuludq %xmm4, %xmm5
pshufd $232, %xmm5, %xmm3 # xmm3 = xmm5[0,2,2,3]
pshufd $245, %xmm4, %xmm4 # xmm4 = xmm4[1,1,3,3]
pmuludq %xmm6, %xmm4
pshufd $232, %xmm4, %xmm4 # xmm4 = xmm4[0,2,2,3]
punpckldq %xmm4, %xmm3 # xmm3 = xmm3[0],xmm4[0],xmm3[1],xmm4[1]
paddd %xmm0, %xmm2
paddd %xmm1, %xmm3
movdqu arr1+4000032(%rax), %xmm1
movdqu arr1+4000048(%rax), %xmm4
movdqu arr2+4000032(%rax), %xmm0
movdqu arr2+4000048(%rax), %xmm5
pshufd $245, %xmm0, %xmm6 # xmm6 = xmm0[1,1,3,3]
pmuludq %xmm1, %xmm0
pshufd $232, %xmm0, %xmm0 # xmm0 = xmm0[0,2,2,3]
pshufd $245, %xmm1, %xmm1 # xmm1 = xmm1[1,1,3,3]
pmuludq %xmm6, %xmm1
pshufd $232, %xmm1, %xmm1 # xmm1 = xmm1[0,2,2,3]
punpckldq %xmm1, %xmm0 # xmm0 = xmm0[0],xmm1[0],xmm0[1],xmm1[1]
pshufd $245, %xmm5, %xmm6 # xmm6 = xmm5[1,1,3,3]
pmuludq %xmm4, %xmm5
pshufd $232, %xmm5, %xmm1 # xmm1 = xmm5[0,2,2,3]
pshufd $245, %xmm4, %xmm4 # xmm4 = xmm4[1,1,3,3]
pmuludq %xmm6, %xmm4
pshufd $232, %xmm4, %xmm4 # xmm4 = xmm4[0,2,2,3]
punpckldq %xmm4, %xmm1 # xmm1 = xmm1[0],xmm4[0],xmm1[1],xmm4[1]
paddd %xmm2, %xmm0
paddd %xmm3, %xmm1
addq $64, %rax
jne .LBB0_3
除了循环标签和实际循环之前的几条指令之外,还有其他内容。
g++ -O2 -S
给出了一组相似的循环,但没有使用SSE指令和展开,所以循环更简单:
.L2:
movl arr1(%rax), %edx
addq $4, %rax
imull arr2-4(%rax), %edx
addl %edx, %ebx
cmpq $4000000, %rax
jne .L2
movl %ebx, %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
xorl %eax, %eax
.p2align 4,,10
.p2align 3
.L3:
movl arr1(%rax), %edx
addq $4, %rax
imull arr2-4(%rax), %edx
addl %edx, %ebx
cmpq $4000000, %rax
jne .L3
movl %ebx, %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
xorl %eax, %eax
popq %rbx
.cfi_def_cfa_offset 8
ret
答案 2 :(得分:0)
你的两个例子非常相似。
var myFormData = new FormData();
myFormData.append('xxx',1233);
console.log(myFormData);
此行乘以sizeof decltype(* arr)并将其添加到arr。这意味着乘法和加法。
如果您希望通过指针算法提高性能,那么下面的内容会更好:
arr[i]
这种新方法消除了乘法的需要。
已编辑以包含替代解决方案:
正如@SimpleVar指出的那样,为了便于阅读,可以选择几个几乎相同的解决方案,具体取决于程序员的偏好。
for (auto p=&(arrP1[0]); p<&(arrP1[n]); p++) // n is size of an array
{
sum += *p; // removed arrP2 for simplification, but can be achieved too.
}
甚至
auto p = arrP1;
for (var i=0; i<n; i++, p++) sum += *p;
使用C ++ 11,还有另一种选择,允许编译器决定如何实现(我们可以假设正确的性能)
for (auto p=arrP1; p<arrP1+n; p++) sum += *p;
(为此,arrP1需要是标准容器,如std :: array或声明的数组:它不能是指针,因为循环不知道数组大小)