数组索引与指针算术性能

时间:2015-12-13 20:40:33

标签: c# arrays pointers

我今天读到了有关数组索引和指针算法的知识,并且知道两个是相同的东西(例如,数组索引是指针算术的语法糖)。

除此之外,其他人说数组索引比指针算法快。 (??)

我想出了这两个代码来测试性能。我在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  

3 个答案:

答案 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或声明的数组:它不能是指针,因为循环不知道数组大小)