在C中,访问我的数组索引更快或通过指针访问更快?

Dav*_*Gao 12 c optimization assembly

在C中,访问数组索引更快或通过指针访问更快?我的意思是更快,哪一个会占用更少的时钟周期.该数组不是常量数组.

tem*_*def 17

它完全取决于系统,哪一个更快,但两者在功能上是相同的,如果一个实际上更快,我会感到非常惊讶.也就是代码

myArr[index]
Run Code Online (Sandbox Code Playgroud)

完全等同于

*(&myArr[0] + index)
Run Code Online (Sandbox Code Playgroud)

同样,写作

*ptr
Run Code Online (Sandbox Code Playgroud)

相当于写作

ptr[0]
Run Code Online (Sandbox Code Playgroud)

大多数编译器都很聪明,可以解决这个问题,所以如果一个比另一个更快,我会感到惊讶.

但更重要的是,你可能不应该太担心这一点.在其他所有工作完成后担心优化问题.如果您发现数组访问确实在扼杀您,那么请考虑寻找更快的替代方案.否则,不要担心; 除非您迫切需要优化,否则拥有干净,可读,可维护的代码比拥有优化代码更有价值.


old*_*mer 13

templatetypedef总结了它.为他的回应增加一些支持.以这些示例函数为例:

unsigned int fun1 ( unsigned int *x )
{
    unsigned int ra,rb;

    rb=0;
    for(ra=0;ra<1000;ra++) rb+=*x++;
    return(rb);
}

unsigned int fun2 ( unsigned int *x )
{
    unsigned int ra,rb;
    rb=0;
    for(ra=0;ra<1000;ra++) rb+=x[ra];
    return(rb);
}

现在gcc制作了这个:

00000000 fun1:
   0:   e52d4004    push    {r4}        ; (str r4, [sp, #-4]!)
   4:   e1a03000    mov r3, r0
   8:   e2804efa    add r4, r0, #4000   ; 0xfa0
   c:   e3a00000    mov r0, #0
  10:   e1a02003    mov r2, r3
  14:   e492c004    ldr ip, [r2], #4
  18:   e5931004    ldr r1, [r3, #4]
  1c:   e2823004    add r3, r2, #4
  20:   e080000c    add r0, r0, ip
  24:   e1530004    cmp r3, r4
  28:   e0800001    add r0, r0, r1
  2c:   1afffff7    bne 10 
  30:   e49d4004    pop {r4}        ; (ldr r4, [sp], #4)
  34:   e12fff1e    bx  lr

00000038 fun2:
  38:   e3a03000    mov r3, #0
  3c:   e1a02003    mov r2, r3
  40:   e790c003    ldr ip, [r0, r3]
  44:   e2833004    add r3, r3, #4
  48:   e7901003    ldr r1, [r0, r3]
  4c:   e2833004    add r3, r3, #4
  50:   e082200c    add r2, r2, ip
  54:   e3530efa    cmp r3, #4000   ; 0xfa0
  58:   e0822001    add r2, r2, r1
  5c:   1afffff7    bne 40 
  60:   e1a00002    mov r0, r2
  64:   e12fff1e    bx  lr

代码不同,但我很遗憾错过了优化的机会.

Clang/llvm制作了这个:


00000000 fun1:
   0:   e3a01000    mov r1, #0
   4:   e3a02ffa    mov r2, #1000   ; 0x3e8
   8:   e1a03001    mov r3, r1
   c:   e2522001    subs    r2, r2, #1
  10:   e490c004    ldr ip, [r0], #4
  14:   e08c3003    add r3, ip, r3
  18:   e2c11000    sbc r1, r1, #0
  1c:   e182c001    orr ip, r2, r1
  20:   e35c0000    cmp ip, #0
  24:   1afffff8    bne c 
  28:   e1a00003    mov r0, r3
  2c:   e12fff1e    bx  lr

00000030 fun2:
  30:   e3a01000    mov r1, #0
  34:   e3a02ffa    mov r2, #1000   ; 0x3e8
  38:   e1a03001    mov r3, r1
  3c:   e2522001    subs    r2, r2, #1
  40:   e490c004    ldr ip, [r0], #4
  44:   e08c3003    add r3, ip, r3
  48:   e2c11000    sbc r1, r1, #0
  4c:   e182c001    orr ip, r2, r1
  50:   e35c0000    cmp ip, #0
  54:   1afffff8    bne 3c
  58:   e1a00003    mov r0, r3
  5c:   e12fff1e    bx  lr

您可能会注意到编译器生成了完全相同的代码,指针或偏移量.通过更改编译器,我比改变指针与数组索引更好.我认为llvm本可以做得更好一点,我需要更多地研究一下,以了解我的代码导致了什么.

编辑:

我希望让编译器至少使用有利于指针的ldr rd,[rs],#4指令,并希望编译器会看到它可以破坏数组地址,从而将其视为指针而非偏移到一个数组(并使用上面的指令,这基本上是clang/llvm所做的).或者,如果它使用ldr rd,[rm,rn]指令执行数组操作.基本上希望其中一个编译器能够生成以下解决方案之一:


funa:
    mov r1,#0
    mov r2,#1000
funa_loop:
    ldr r3,[r0],#4
    add r1,r1,r3
    subs r2,r2,#1
    bne funa_loop
    mov r0,r1
    bx lr

funb:
    mov r1,#0
    mov r2,#0
funb_loop:
    ldr r3,[r0,r2]
    add r1,r1,r3
    add r2,r2,#4
    cmp r2,#0x4000
    bne funb_loop
    mov r0,r1
    bx lr

func:
    mov r1,#0
    mov r2,#4000
    subs r2,r2,#4
func_loop:
    beq func_done
    ldr r3,[r0,r2]
    add r1,r1,r3
    subs r2,r2,#4
    b func_loop
func_done:
    mov r0,r1
    bx lr

没有完全到达那里,但非常接近.这是一个有趣的练习.注意以上是所有ARM汇编程序.

一般来说,(不是我特定的C代码示例,不一定是ARM),一些流行的体系结构,你将从基于寄存器的地址(ldr r0,[r1])和带有寄存器索引/偏移量的负载加载(ldr r0,[r1,r2])其中地址是两个寄存器的总和.理想情况下,一个寄存器是数组的基址,第二个是索引/偏移量.寄存器的前一个加载适用于指针,后者适用于数组.如果你的C程序不会改变或移动指针或索引,那么在这两种情况下,这意味着计算的静态地址然后使用正常加载,数组和指针都应该产生相同的指令.有关更改指针/索引的更有趣的情况.

Pointer

ldr r0,[r1]
...
add r1,r1,some number

Array index

ldr r0,[r1,r2]
...
add r2,r2,some number

(用商店替换负载,根据需要用sub添加)

有些架构没有三个寄存器寄存器索引指令,因此您必须执行类似的操作

array index:
mov r2,r1
...
ldr r0,[r2]
...
add r2,r2,some number

或者取决于编译器,它可能会变得非常糟糕,尤其是如果你编译调试或没有优化,并假设你没有三个寄存器添加

array index:
mov r2,#0
...
mov r3,r1
add r3,r2
ldr r4,[r3]
...
add r2,some number

所以这两种方法很可能是相同的.正如在ARM上看到的那样,它可以将两个(在立即限制内)指针指令合并为一个,使得速度更快一些.数组索引解决方案会烧掉更多的寄存器,并且取决于架构的可用寄存器数量,这些寄存器会促使您更快更频繁地将寄存器交换到堆栈(比指针更多),从而减慢您的速度.如果你不介意破坏基地址,那么底线是指针解决方案可能会从性能角度给你一个优势.它与您的代码和编译器有很大关系.对我来说,它的可读性发挥作用,我觉得数组更容易阅读和遵循,第二,我需要保留该指针以释放malloc或再次通过该内存,等等.如果是这样,我可能会使用一个数组与一个索引,如果它是一次通过而我不关心破坏基地址我将使用指针.正如您在上面看到的编译器生成的代码,如果性能至关重要,那么无论如何都要在汇编程序中手动编写解决方案(基于建议的方法,让编译器首先尝试它).


Oys*_*ein 6

简单的索引操作在我曾经触及的每个编译器上编译为相同的机器代码.通常建议使用索引以提高可读性.

需要根据具体情况检查涉及指针访问与数组索引的不同逻辑的更复杂情况.如果您有疑问,请一如既往地描述您的代码.