6

在我的嵌入式系统课程中,我们被要求将给定的 C 函数 AbsVal 重新编码为 ARM 程序集。有人告诉我们,我们能做的最好的就是 3 行。我决心找到一个 2 线解决方案并最终做到了,但我现在的问题是我实际上是降低了性能还是提高了性能

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/教授的3线解决方案

ASR R1, R0, #31         ; R1 <- (x >= 0) ? 0 : -1
ADD R0, R0, R1          ; R0 <- R0 + R1
EOR R0, R0, R1          ; R0 <- R0 ^ R1

我的 2 线解决方案

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. 删除一个内存提取

那么,哪一个实际上更快?它取决于处理器或内存访问速度吗?

4

2 回答 2

6

这是另外两个指令版本:

    cmp     r0, #0
    rsblt   r0, r0, #0

转换为简单的代码:

  if (r0 < 0)
  {
    r0 = 0-r0;
  }

该代码应该非常快,即使在 Cortex-A8 和 A9 等现代 ARM-CPU 内核上也是如此。

于 2013-05-13T16:55:49.227 回答
4

访问 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 个字节。

因此,您实际上增加了代码的大小,而不是删除内存提取。

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

于 2013-05-13T23:11:30.977 回答