为什么循环展开会在 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)
。
那么您应该会看到更容易解释的几个百分点的加速。
我正在使用以下代码在 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)
。
那么您应该会看到更容易解释的几个百分点的加速。