为什么循环展开会在 ARM Cortex-a53 上带来如此大的加速?

Why loop unroll brings so much speedup on ARM Cortex-a53?

我正在使用以下代码在 ARM Cortex-a53 处理器 运行AArch64 状态下循环展开:

void do_something(uint16_t* a, uint16_t* b, uint16_t* c, size_t array_size)
{
  for (int i = 0; i < array_size; i++)
  {
    a[i] = a[i] + b[i];
    c[i] = a[i] * 2;
  }
}

使用标志 -O1,我得到了以下程序集,

.L3:
    ldrh    w3, [x0, x4]
    ldrh    w5, [x1, x4]
    add     w3, w3, w5
    and     w3, w3, 65535
    strh    w3, [x0, x4]
    ubfiz   w3, w3, 1, 15
    strh    w3, [x2, x4]
.LVL2:
    add x4, x4, 2
.LVL3:
    cmp x4, x6
    bne .L3

在 162 毫秒内完成(a、b、c 的大小都很大)。为简单起见,我在循环之前省略了一些 prolog 和 epilog 代码,但它们仅用于堆栈设置等。

然后我展开循环,结果代码如下:

void add1_opt1(uint16_t* a, uint16_t* b, uint16_t* c, size_t array_size)
{
  for (int i = 0; i < array_size/4; i+=4)
  {
    a[i]   = a[i] + b[i];
    c[i]   = a[i] * 2;
    a[i+1] = a[i+1] + b[i+1];
    c[i+1] = a[i+1] * 2;
    a[i+2] = a[i+2] + b[i+2];
    c[i+2] = a[i+2] * 2;
    a[i+3] = a[i+3] + b[i+3];
    c[i+3] = a[i+3] * 2;
  }
}

它给出如下汇编(仍然使用 -O1,因为使用 -O0 编译器做了一些愚蠢的事情):

.L7:
    ldrh    w1, [x0]
    ldrh    w5, [x3]
    add w1, w1, w5
    and w1, w1, 65535
    strh    w1, [x0]
    ubfiz   w1, w1, 1, 15
    strh    w1, [x2]
    ldrh    w1, [x0, 2]
    ldrh    w5, [x3, 2]
    add w1, w1, w5
    and w1, w1, 65535
    strh    w1, [x0, 2]
    ubfiz   w1, w1, 1, 15
    strh    w1, [x2, 2]
    ldrh    w1, [x0, 4]
    ldrh    w5, [x3, 4]
    add w1, w1, w5
    and w1, w1, 65535
    strh    w1, [x0, 4]
    ubfiz   w1, w1, 1, 15
    strh    w1, [x2, 4]
    ldrh    w1, [x0, 6]
    ldrh    w5, [x3, 6]
    add w1, w1, w5
    and w1, w1, 65535
    strh    w1, [x0, 6]
    ubfiz   w1, w1, 1, 15
    strh    w1, [x2, 6]
.LVL8:
    add x4, x4, 4
.LVL9:
    add x0, x0, 8
    add x3, x3, 8
    add x2, x2, 8
    cmp x4, x6
    bcc .L7

这几乎就像复制粘贴其他汇编代码 4 次。问题是,为什么这段代码只用了 28 毫秒就变成了 运行,这相当于提速了 5 倍。对于像这样的简单循环条件,我认为分支预测应该在两种代码中都做得很好,对吗?而在第二个汇编代码中,存储也是交错的。所以我无法想象这样的代码如何获得如此大的加速。

问题在这里:for (int i = 0; i < array_size/4; i+=4).

循环直到 array_size/4 将完成四分之一的工作。

应该是for (int i = 0; i < array_size; i+=4)

那么您应该会看到更容易解释的几个百分点的加速。