在我的嵌入式系统课程中,我们被要求将给定的c函数AbsVal重新编码为ARM汇编程序。我们被告知我们最多只能写3行。我决定找到一个两行的解决方案,并最终做到了,但是我现在的问题是,我是否实际上降低了性能还是提高了性能。(特别是在Cortex-M3上)
c代码:
unsigned long absval(signed long x){
unsigned long int signext;
signext = (x >= 0) ? 0 : -1; //This can be done with an ASR instruction
return (x + signet) ^ signext;
}
TA/Professor的3行解决方案
ASR R1, R0, #31 ; R1 <- (x >= 0) ? 0 : -1
ADD R0, R0, R1 ; R0 <- R0 + R1
EOR R0, R0, R1 ; R0 <- R0 ^ R1
我的两行解
ADD R1, R0, R0, ASR #31 ; R1 <- x + (x >= 0) ? 0 : -1
EOR R0, R1, R0, ASR #31 ; R0 <- R1 ^ (x >= 0) ? 0 : -1
有几个地方我可以看到潜在的性能差异:
- 增加一个额外的算术右移调用
- 删除一个内存取回
那么,哪个更快呢?它是否取决于处理器或内存访问速度?
下面是另一个双指令版本:
cmp r0, #0
rsblt r0, r0, #0
转换成简单的代码:
if (r0 < 0)
{
r0 = 0-r0;
}
即使在Cortex-A8和A9等现代arm cpu内核上,该代码也应该相当快。
在Thumb模式(Cortex-M3上唯一支持的模式)中,它实际上需要三条指令,因为it
(If Then)需要谓词另一条指令。所有这些都是16位指令。
.syntax unified
cmp r0, #0
it lt
rsblt r0, r0 ,#0
转到ARM.com并获取Cortex-M3数据表。第3-4页的3.3.1节给出了指令时序。幸运的是,它们在Cortex-M3上非常简单。
我们可以从这些时间看出,在一个完美的"无等待状态"系统中,你的教授的例子需要3个周期:
ASR R1, R0, #31 ; 1 cycle
ADD R0, R0, R1 ; 1 cycle
EOR R0, R0, R1 ; 1 cycle
; total: 3 cycles
和你的版本需要两个循环:
ADD R1, R0, R0, ASR #31 ; 1 cycle
EOR R0, R1, R0, ASR #31 ; 1 cycle
; total: 2 cycles
所以,从理论上讲,你的更快。
你提到"删除一个内存提取",但这是真的吗?各自的例程有多大?因为我们处理的是Thumb-2,所以我们有16位和32位的混合指令可用。让我们看看它们是如何组合的:
它们的版本(根据UAL语法调整):
.syntax unified
.text
.thumb
abs:
asrs r1, r0, #31
adds r0, r0, r1
eors r0, r0, r1
组装:
00000000 17c1 asrs r1, r0, #31
00000002 1840 adds r0, r0, r1
00000004 4048 eors r0, r1
3x2 = 6字节
您的版本(再次根据UAL语法调整):
.syntax unified
.text
.thumb
abs:
add.w r1, r0, r0, asr #31
eor.w r0, r1, r0, asr #31
组装:
00000000 eb0071e0 add.w r1, r0, r0, asr #31
00000004 ea8170e0 eor.w r0, r1, r0, asr #31
这是2x4 = 8字节
所以你并没有删除内存提取,而是增加了代码的大小。
但是这会影响性能吗?我的建议是对进行基准测试