我拿了实例(发表评论)和put it on the godbolt compiler explorer。 calc()
中主要的低效率是src1
和src2
是全局变量,它必须从内存加载,而不是寄存器中传递的参数。
我没有看main
,只是calc
。
register int sum asm ("r4");
register int r asm ("r5");
register int c asm ("r6");
register int k asm ("r7");
register int temp1 asm ("r8"); // really? you're using two global register vars for scratch temporaries? Just let the compiler do its job.
register int temp2 asm ("r9");
register long n asm ("r10");
int *src1, *src2, *dst;
void calc() {
temp1 = r*n;
temp2 = k*n;
temp1 = temp1+k;
temp2 = temp2+c;
// you get bad code for this because src1 and src2 are globals, not args passed in regs
sum = sum + src1[temp1] * src2[temp2];
}
# gcc 4.8.2 -O3 -Wall -Wextra -Wa,-a,-ad -fverbose-asm
mla r0, r10, r7, r6 @ temp2.9, n, k, c @@ tmp = k*n + c
movw r3, #:lower16:.LANCHOR0 @ tmp136,
mla r8, r10, r5, r7 @ temp1, n, r, k @@ temp1 = r*n + k
movt r3, #:upper16:.LANCHOR0 @ tmp136,
ldmia r3, {r1, r2} @ tmp136,, @@ load both pointers, since they're stored adjacently in memory
mov r9, r0 @ temp2, temp2.9 @@ This insn is wasted: the first MLA should have had this as the dest
ldr r3, [r1, r8, lsl #2] @ *_22, *_22
ldr r2, [r2, r9, lsl #2] @ *_28, *_28
mla r4, r2, r3, r4 @ sum, *_28, *_22, sum
bx lr @
出于某种原因,整数乘法累加中的一个(mla
)指令使用r8
(temp1
)作为目的地,而另一个写入r0
(划痕REG),并且只是后来移动结果到r9
(temp2
)。
该sum += src1[temp1] * src2[temp2]
完成mla
读取和写入r4
(sum
)。
为什么你需要temp1
和temp2
为全局变量?这只会阻止优化器进行积极的优化,而这些优化不会计算出与C源完全相同的临时对象。幸运的是,C存储器模型足够弱,它应该能够重新排列它们的赋值,但这可能实际上是它直接将它转换为temp2
的原因,因为它决定首先进行这种计算。 (嗯,内存模型是否适用?其他线程根本看不到我们的寄存器,所以这些全局变量都是线程本地的,它应该允许对全局变量进行宽松的排序,信号处理程序可以看到这些全局变量, gcc并没有遵循严格的源代码顺序,因为源码中的两个乘法都是在添加之前发生的。)
Godbolt没有更新的ARM gcc版本,所以我不能轻易测试更新的GCC。一个新的gcc可能会在这方面做得更好。
顺便说一句,I tried a version of the function using local variables for temporaries, and didn't actually get better results。可能是因为仍然有很多注册全局变量,gcc无法为临时对象选择方便的注册表。
// same register globals, except for temp1 and temp2.
void calc_local_tmp() {
int t1 = r*n + k;
sum += src1[t1] * src2[k*n + c];
}
push {lr} @ gcc decides to push to get a tmp reg
movw r3, #:lower16:.LANCHOR0 @ tmp131,
mla lr, r10, r5, r7 @ tmp133, n.1, r, k.2
movt r3, #:upper16:.LANCHOR0 @ tmp131,
mla ip, r7, r10, r6 @ tmp137, k.2, n.1, c
ldr r2, [r3] @ src1, src1
ldr r0, [r3, #4] @ src2, src2
ldr r1, [r2, lr, lsl #2] @ *_10, *_10
ldr r3, [r0, ip, lsl #2] @ *_20, *_20
mla r4, r3, r1, r4 @ sum, *_20, *_10, sum
ldr pc, [sp], #4 @
用-fcall-used-r8 -fcall-used-r9
编译没有帮助; gcc使相同的代码推动lr
获得额外的临时性。它无法使用ldmia
(加载倍数),因为它无法选择哪个临时放置在哪个reg中。(&src1
在r0
会让它加载src1
和src2
到r2
和r3
)
奇怪GCC决定不直接返回5。 – user3528438
@ user3528438:对我来说'movs r0,#5; bx lr'。 – EOF
关于-O3的观察与另一个代码一起使用,导致额外的MOV。使用此代码,它直接返回5。 – gma