2

这是一段这样的代码:

// example_3
int Add_8K_3(int* in, int* out, int b)
{
    int i;
    for(i=0;i<1024;i++)
    {
        int a0, a1;
        a0 = *in++;
        a1 = *in++;
        *out++ = a0 + b;
        *out++ = a1 + b;
    }
    return 0;
}

我通过 ARMCC 和 Xcode(通过 -O3)编译它。但是两种结果的表现却大不相同。Xcode 中的循环数大约是 armcc 结果的 3 倍。手臂汇编代码

    {
                    Add_8K_3 PROC
    ADD      r0,r0,#4
    MOV      r3,#0x400
    PUSH     {r4}                  ;3264
|L1.12|
    SUBS     r3,r3,#1
    LDR      r4,[r0,#-4]           ;3271
    LDR      r12,[r0],#8           ;3271
    ADD      r4,r4,r2              ;3271
    STR      r4,[r1],#8
    ADD      r12,r12,r2
    STR      r12,[r1,#-4]
    BNE      |L1.12|
    POP      {r4}
    MOV      r0,#0
    BX       lr
                    ENDP
}

Xcode 汇编代码

    {
_Add_8K_3:
    .cfi_startproc
Lfunc_begin3:
    .loc    1 77 0                  @ /Users/Emedia/Desktop/testperformance/testperformance/core.c:77:0
@ BB#0:
    .loc    1 76 19 prologue_end    @ /Users/Emedia/Desktop/testperformance/testperformance/core.c:76:19
    push    {r7, lr}
    mov.w   lr, #0
Ltmp15:
    @DEBUG_VALUE: i <- 0+0
    mov r7, sp
    @DEBUG_VALUE: Add_8K_3:in <- R0+0
    @DEBUG_VALUE: Add_8K_3:out <- R1+0
    @DEBUG_VALUE: Add_8K_3:b <- R2+0
LBB3_1:                                 @ =>This Inner Loop Header: Depth=1
Ltmp16:
    @DEBUG_VALUE: Add_8K_3:in <- R0+0
    @DEBUG_VALUE: Add_8K_3:out <- R1+0
    @DEBUG_VALUE: Add_8K_3:b <- R2+0
    @DEBUG_VALUE: i <- 0+0
    .loc    1 82 9                  @ /Users/Emedia/Desktop/testperformance/testperformance/core.c:82:9
    ldr.w   r12, [r0, lr, lsl #3]
    Ltmp17:
@DEBUG_VALUE: a0 <- R12+0
    add.w   r3, r0, lr, lsl #3
    .loc    1 83 9                  @ /Users/Emedia/Desktop/testperformance/testperformance/core.c:83:9
    ldr.w   r9, [r3, #4]
Ltmp18:
    @DEBUG_VALUE: a1 <- R9+0
    .loc    1 86 9                  @ /Users/Emedia/Desktop/testperformance/testperformance/core.c:86:9
    add.w   r3, r12, r2
    str.w   r3, [r1, lr, lsl #3]
    add.w   r12, r1, lr, lsl #3
    Ltmp19:
    .loc    1 79 20                 @ /Users/Emedia/Desktop/testperformance/testperformance/core.c:79:20
    add.w   lr, lr, #1
    Ltmp20:
@DEBUG_VALUE: i <- LR+0
    .loc    1 87 9                  @ /Users/Emedia/Desktop/testperformance/testperformance/core.c:87:9
    add.w   r3, r9, r2
    str.w   r3, [r12, #4]
Ltmp21:
    .loc    1 79 9                  @ /Users/Emedia/Desktop/testperformance/testperformance/core.c:79:9
    cmp.w   lr, #1024
    bne LBB3_1
Ltmp22:
    @ BB#2:
    movs    r0, #0
    .loc    1 93 5                  @ /Users/Emedia/Desktop/testperformance/testperformance/core.c:93:5
    pop {r7, pc}
Ltmp23:
Lfunc_end3:
}

我可能遇到的问题是 Xcode 无法通过合适的 asm 代码解析“in[X]”。我的问题是:(1)我如何编写 C 代码来生成 Xcode 可以将“in[X]”解析为合适的 asm 代码(如 arm)的 asm 代码?(2) 是否有一些手册来描述编译器与clang 和armcc 的区别,并告诉我如何通过高性能编写iOS 的C 代码。

谢谢。

4

3 回答 3

1

这是矢量化的完美问题。正如 Jake 所指出的,向量化有时会被避免,因为每个架构都有单独的代码路径是很痛苦的。在理想的世界中,编译器会成功地自动向量化所有这些情况,这不会成为问题。与此同时,还有一些其他选项可用。

如果你的目标是 iOS / OSX 并且可以限制自己使用 clang,那么对于像这样的简单循环,最好的解决方案是使用 clang “扩展向量”;这些使您可以编写跨架构工作的矢量代码:

typedef int vector_int __attribute__((ext_vector_type(4),aligned(4)));
const int ints_per_vector = 4;

int Add_8K_3(int *in, int* out, int b) {
  vector_int *vin  = (vector_int *)in;
  vector_int *vout = (vector_int *)out;
  for (int i=0; i<1024/ints_per_vector; i++)
    vout[i] = vin[i] + b;
  return 0;
}

这会为 clang 支持的所有架构生成体面(不完美)的矢量代码。例如armv7s:

0: adds      r3,         r0, r2
   vld1.32  {d18, d19}, [r3]
   adds      r3,         r1, r2
   adds      r2,         #0x10
   cmp.w     r2,         #0x1000
   vadd.i32  q9,         q9, q8
   vst1.32  {d18, d19}, [r3]
   bne       0b

手臂64:

0: ldr       q1,   [x0, x8, lsl #4]
   add.4s    v1,    v1, v0
   str       q1,   [x1, x8, lsl #4]
   add       x8,    x8, 1
   cmp       w8,    #256
   b.ne      0b

x86_64:

0: movdqu   (%rdi,%rax), %xmm1
   paddd     %xmm0,      %xmm1
   movdqu    %xmm1,     (%rsi,%rax)
   add       $0x10,      %rax
   cmp       $0x1000,    %eax
   jne       0b

但是,如果您需要将代码移植到其他编译器,则最好使用内部函数或依赖编译器优化,并且如果您确实需要代码尽可能快地运行,那么一些手动调整是不可避免的.

于 2013-11-13T15:47:00.743 回答
1

虽然 Apple 的 LLVM 生成了不错的代码(比 GCC 好得多),但它仍然无法与一流的 ARMCC 相匹敌。

事实上,ARMCC 真的很不错。

但是,您的示例非常简单,即使是臭名昭著的 GCC 也不会失败。

您的代码有两件事搞砸了:

  1. ARMCC 在 ARM 模式下完成这项工作,LLVM 在 Thumb2 中完成
  2. XCode 二进制文件仍然包含调试/分析信息。您应该更改相应的选项。

NEON 可以更快地处理示例函数,但是对于 iOS 部署,您需要 AARCH32 和 AARCH64 版本,并且两者在语法和寄存器分配方面有很大不同。

如果您正在尝试学习 ARM 汇编以进行优化,请不要这样做。AARCH64 的 ISA 完全重构和精简,这意味着即使是世界上最糟糕的编译器,如 GCC,也能够生成一半体面的代码。

不过,NEON 是另一回事。这可能是值得的。(但您必须同时编写 AARCH32 和 AARCH64 版本)

于 2013-11-13T11:58:28.220 回答
0

我只能在这里猜测:

看来,clang 优化器更喜欢使用“立即偏移”来访问输入和输出数组。例如

str.w   r3, [r1, lr, lsl #3]

如果偏移量超过1024,clang 优化器会选择寄存器加载并使用寄存器偏移量进行存储:

这是访问整数范围超过 1024(在本例中为 1025)时的反汇编:

0x79188:  push   {r4, r5, r7, lr}
0x7918a:  add    r7, sp, #8
0x7918c:  str    r8, [sp, #-4]!
0x79190:  movw   r3, #1025
0x79194:  ldrd   r8, r9, [r0]
0x79198:  add.w  r5, r9, r2
0x7919c:  adds   r0, #8
0x7919e:  subs   r3, #1
0x791a0:  add.w  r4, r8, r2
0x791a4:  strd   r4, r5, [r1]
0x791a8:  add.w  r1, r1, #8
0x791ac:  bne    0x79194                   ; Add_8K_3 + 12 at AppDelegate.m:24
0x791ae:  movs   r0, #0
0x791b0:  ldr    r8, [sp], #4
0x791b4:  pop    {r4, r5, r7, pc}

当访问整数的范围超过 1800 ( for (int i = 0; i < 1800; ++i)) 时,clang 将再次选择使用“立即偏移”和单个寄存器加载和存储:

0x94180:  push   {r7, lr}
0x94182:  mov.w  lr, #0
0x94186:  mov    r7, sp
0x94188:  ldr.w  r12, [r0, lr, lsl #3]
0x9418c:  add.w  r3, r0, lr, lsl #3
0x94190:  ldr.w  r9, [r3, #4]
0x94194:  add.w  r3, r12, r2
0x94198:  str.w  r3, [r1, lr, lsl #3]
0x9419c:  add.w  r12, r1, lr, lsl #3
0x941a0:  add.w  lr, lr, #1
0x941a4:  add.w  r3, r9, r2
0x941a8:  str.w  r3, [r12, #4]
0x941ac:  cmp.w  lr, #1800
0x941b0:  bne    0x94188                   ; Add_8K_3 + 8 at AppDelegate.m:24
0x941b2:  movs   r0, #0
0x941b4:  pop    {r7, pc}

不确定,为什么-也许启发式表明最佳性能。

于 2013-11-13T12:35:39.777 回答