我的意思是说,哪一个会花更少的时钟周期.
数组不是一个常量数组.
解决方法
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,ip 24: e1530004 cmp r3,r4 28: e0800001 add 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,r3] 4c: e2833004 add r3,#4 50: e082200c add r2,ip 54: e3530efa cmp r3,#4000 ; 0xfa0 58: e0822001 add 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,#1 10: e490c004 ldr ip,[r0],#4 14: e08c3003 add r3,ip,r3 18: e2c11000 sbc r1,r1,#0 1c: e182c001 orr ip,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,#1 40: e490c004 ldr ip,#4 44: e08c3003 add r3,r3 48: e2c11000 sbc r1,#0 4c: e182c001 orr ip,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,#4 add r1,r3 subs r2,#1 bne funa_loop mov r0,r1 bx lr funb: mov r1,#0 funb_loop: ldr r3,r2] add r1,r3 add r2,#4 cmp r2,#0x4000 bne funb_loop mov r0,r1 bx lr func: mov r1,#4000 subs r2,#4 func_loop: beq func_done ldr r3,#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,some number Array index ldr r0,r2] ... add r2,some number
一些架构没有三个注册寄存器索引指令,所以你必须做一些事情
array index: mov r2,r1 ... ldr r0,[r2] ... add r2,some number
或者根据编译器可能会变得非常糟糕,esp如果编译用于调试或没有优化,并假设您没有三个寄存器添加
array index: mov r2,#0 ... mov r3,r1 add r3,r2 ldr r4,[r3] ... add r2,some number
所以这两种方法很可能是相等的.如在ARM上看到的,它可以将两个(即时)指针指针组合在一起,使其更快一点.阵列索引解决方案会烧写更多的寄存器,并且取决于架构的可用寄存器数量,这些寄存器可能会将寄存器交换到堆栈的时间越来越快(比使用指针)更慢.如果您不介意摧毁基地址,底线是指针解决方案可能会从性能角度给您一个优势.它与您的代码和编译器有很大关系.对我来说,它的可读性发挥作用,我觉得数组更容易阅读和跟随,第二个我需要保留该指针释放一个malloc或再次通过该内存等.如果是这样我可能会使用一个数组与一个索引,如果是一次性通过,我不关心摧毁基地址,我将使用一个指针.如上所述,使用编译器生成的代码,如果性能至关重要,那么手工编译汇编器中的解决方案(根据建议的方法,让编译器先尝试一下).