ARM汇编:绝对值函数:两行还是三行更快?

7
在我的嵌入式系统课程中,我们被要求将给定的 C 函数 AbsVal 重新编码为 ARM 汇编语言。 我们被告知最好的解决方案是三行代码。我决心找到一个两行的解决方案,最终也确实找到了,但是我现在的问题是我是否真的降低了性能或提高了性能。(特别是在 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/教授的三行解决方案
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

有几个地方可能会出现性能差异:
1. 增加一个额外的算术右移调用 2. 移除一个内存获取操作
那么,到底哪个更快呢?这取决于处理器还是内存访问速度?

1
为什么不进行基准测试呢?这是了解性能差异的最可靠方式。 - jpaugh
3
这取决于具体的实现。A8之前的核心可能会更快地运行您的代码,移位通常是免费的,除非您的移位值在寄存器中。 A8和更新的核心具有多个管道,这可能允许一些并行执行,这可能使两者在周期方面相等,您可以尝试使用http://pulsar.webshaker.net/ccc/result.php?lng=us。 - Nico Erfurth
@KenW 越来越多的情况下(至少对于像x86这样的CISC芯片),基准测试是了解性能的唯一明智方法。(性能配置文件基本上是不确定的)。话虽如此,我对ARM知之甚少;祝你好运。 - jpaugh
3
在我的观点中,倒置条件减法是做这件事的方法。由于ARM状态寄存器只有在需要时才更新,因此可以在更早的阶段通过副作用计算出符号位。实际上,rsbne可以被认为是计算绝对值的单个指令方法。可以使用movs r0, variable来同时从内存加载值并测试符号。 - Aki Suihkonen
@DavidThomas 这个问题是关于Cortex-M3的ARM Thumb-2指令集,因为这就是我们LM3S811所使用的。我的指令和我的教授都在Thumb-2 LM3S811上成功运行过。 - Ken W
显示剩余3条评论
2个回答

7
这是另外一个两步指南版本:
    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

我下次去实验室时会测试这个,并将你的与我的进行基准测试。如果它更快,那么我会接受这个答案 :) - Ken W
结果很遗憾,这无法在Thumb-2上汇编,但对于ARM来说是可行的。 - Ken W
1
我相信语法已经改变了,应该是这样的(哇,注释中的代码真的不起作用?):2800 cmp r0, #0; BFB8 it lt; 4240 rsblt r0, r0 ,#0 - Aksel

5
请前往ARM.com并下载Cortex-M3数据手册。第3.3.1节在第3-4页上列出了指令时间。幸运的是,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字节。

因此,您并没有减少内存获取,而是增加了代码的大小。

但这会影响性能吗?我的建议是进行基准测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接