第22部分- Linux ARM汇编分支指令
在32位ARM系统中,可以通过指令本身中的条件字段来支持的分支判断。 例如在T32中,有用于构建条件序列的IT(如果-则-then)指令。 A64不支持此功能,但有一组不同的特定条件说明。
32位预测分支
源代码如下:
.text
.global main
main:
mov r1, #123 /* r1 ← 123 */
mov r2, #0 /* r2 ← 0 */
loop:
cmp r1, #1 /* compare r1 and 1 */
beq end /* branch to end if r1 == 1 */
and r3, r1, #1 /* r3 ← r1 & 1 */
cmp r3, #0 /* compare r3 and 0 */
bne odd /* branch to odd if r3 != 0 */
even:
mov r1, r1, ASR #1 /* r1 ← (r1 >> 1) */
b end_loop
odd:
add r1, r1, r1, LSL #1 /* r1 ← r1 + (r1 << 1) */
add r1, r1, #1 /* r1 ← r1 + 1 */
end_loop:
add r2, r2, #1 /* r2 ← r2 + 1 */
b loop /* branch to loop */
end:
mov r0, r2
bx lr
as -g -o collatz.o collatz.s
gcc -o collatz collatz.o
使用减少分支的指令后,代码如下:
分支指令代码
.text
.global main
main:
mov r1, #123 /* r1 ← 123 */
mov r2, #0 /* r2 ← 0 */
loop:
cmp r1, #1 /* compare r1 and 1 */
beq end /* branch to end if r1 == 1 */
and r3, r1, #1 /* r3 ← r1 & 1 */
cmp r3, #0 /* compare r3 and 0 */
moveq r1, r1, ASR #1 /* r1 ← (r1 >> 1) */
addne r1, r1, r1, LSL #1 /* r1 ← r1 + (r1 << 1) */
addne r1, r1, #1 /* r1 ← r1 + 1 */
add r2, r2, #1 /* r2 ← r2 + 1 */
b loop /* branch to loop */
end:
mov r0, r2
bx lr
减少了分支目录。
as -g -o collatz-b.o collatz-b.s
gcc -o collatz-b collatz-b.o
在树莓派上安装linux-tools工具进行判断两者差异。
执行:
perf_4.9 stat --log-fd=3 --repeat=50 -e cpu-clock ./collatz 3>&1
Performance counter stats for './collatz' (50 runs):
2.492647 cpu-clock (msec) # 0.530 CPUs utilized ( +- 0.53% )
0.004699564 seconds time elapsed ( +- 0.37% )
执行:
perf_4.9 stat --log-fd=3 --repeat=50 -e cpu-clock ./collatz-b 3>&1
Performance counter stats for './collatz-b' (50 runs):
2.119385 cpu-clock (msec) # 0.530 CPUs utilized ( +- 3.64% )
0.003997353 seconds time elapsed ( +- 3.56% )
可以看到差异。
64位预测分支
.arch armv8-a
.global _start
.text
_start:
mov x1, #123 /* r1 ← 123 */
mov x2, #0 /* r2 ← 0 */
loop:
cmp x1, #1 /* compare r1 and 1 */
beq end /* branch to end if r1 == 1 */
and x3, x1, #1 /* r3 ← r1 & 1 */
cmp x3, #0 /* compare r3 and 0 */
bne odd /* branch to odd if r3 != 0 */
even:
mov x1, x1, ASR #1 /* r1 ← (r1 >> 1) */
b end_loop
odd:
add x1, x1, x1, LSL #1 /* r1 ← r1 + (r1 << 1) */
add x1, x1, #1 /* r1 ← r1 + 1 */
end_loop:
add x2, x2, #1 /* r2 ← r2 + 1 */
b loop /* branch to loop */
end:
mov x0,x2
mov x8, 93
svc 0
as -g -o collatz64.o collatz64.s
ld -o collatz64 collatz64.o
分支优化代码
A64不支持此功能,但有一组不同的特定条件说明。
A64指令集不支持每条指令的条件执行。 指令的预定执行不能提供足够的好处来证明其大量使用操作码空间。
A64指令集仅允许有条件地执行程序流控制分支指令。 这与A32和T32相反,在A32和T32中,大多数指令都可以使用条件代码进行判断。 这些可以总结如下。
条件选择(move)
CSEL根据条件在两个寄存器之间进行选择。 无条件指令紧跟条件选择,可以代替较短的条件序列。
CSINC根据条件在两个寄存器之间进行选择。 返回第一个源寄存器或第二个源寄存器加一个。
CSINV根据条件在两个寄存器之间进行选择。 返回第一个源寄存器或反向的第二个源寄存器。
CSNEG根据条件在两个寄存器之间进行选择。 返回第一个源寄存器或取反的第二个源寄存器。
条件集
有条件地在0和1(CSET)或0和-1(CSETM)之间选择。 例如,用于在通用寄存器中将条件标志设置为布尔值或掩码。
条件比较
(CMP和CMN)如果原始条件为true,则将条件标志设置为比较结果。 如果不为真,则将条件标志设置为指定的条件标志状态。 条件比较指令对于表达嵌套或复合比较非常有用。
使用FCSEL和FCCMP指令的浮点寄存器也可以使用条件选择和条件比较。
例如:
CSINC X0, X1, X0, NE // 如果清除零标志,则将返回寄存器X0设置为X1,否则将X0递增
CINC X0, X0, LS // 如果小于或等于(LS),则X0 = X0 + 1
CSET W0, EQ // 如果先前的比较等于(Z = 1),则W0 = 1,否则W0=0
CSETM X0, NE // 如果不相等 X0 = -1, 否则X0 = 0
代码示例:
if (i == 0) r = r + 2; else r = r - 1;
汇编实现如下:
CMP w0, #0 // if (i == 0)
SUB w2, w1, #1 // r = r - 1
ADD w1, w1, #2 // r = r + 2
CSEL w1, w1, w2, EQ // select between the two results
CMP命令可以更新cpsr寄存器。
优化代码
.arch armv8-a
.global _start
.text
_start:
mov x1, #123 /* r1 ← 123 */
mov x2, #0 /* r2 ← 0 */
loop:
cmp x1, #1 /* compare r1 and 1 */
beq end /* branch to end if r1 == 1 */
and x3, x1, #1 /* r3 ← r1 & 1 */
cmp x3, #0 /* compare r3 and 0 */
mov x5,x1 //保存x1到x5
add x1, x1, x1, LSL #1 /* 奇数操作:r1 ← r1 + (r1 << 1) */
add x2, x1, #1 /*奇数操作:r1 ← r1 + 1 ,保存到x2*/
mov x4, x5, ASR #1 /* 偶数操作 保存到X4*/
CSINC X1, X4, X2, EQ //等于0,则X1=X2,否则等于X4
end_loop:
add x2, x2, #1 /* r2 ← r2 + 1 */
b loop /* branch to loop */
end:
mov x0,x2
mov x8, 93
svc 0
as -g -o collatz64o.o collatz64o.s
ld -o collatz64o collatz64o.o
运行得到结果如下:
./collatz64o
#]$echo $?
46
#perf stat --log-fd=3 --repeat=5000 -e cpu-clock ./collatz64 3>&1
Performance counter stats for './collatz64' (5000 runs):
0.049073 cpu-clock (msec) # 0.188 CPUs utilized ( +- 0.14% )
0.000260505 +- 0.000000395 seconds time elapsed ( +- 0.15% )
#perf stat --log-fd=3 --repeat=5000 -e cpu-clock ./collatz64o 3>&1
Performance counter stats for './collatz64o' (5000 runs):
0.048612 cpu-clock (msec) # 0.191 CPUs utilized ( +- 0.13% )
0.000254531 +- 0.000000383 seconds time elapsed ( +- 0.15% )
可以看到collatz64o有些优势。