提问人:Bernard 提问时间:7/26/2022 最后编辑:Peter CordesBernard 更新时间:7/27/2022 访问量:2977
在 x86 程序集中取两个有符号整数的平均值的最快方法?
Fastest way to take the average of two signed integers in x86 assembly?
问:
假设我们有两个寄存器长度为2 的有符号1 整数,比如 和 。我们想计算值,可以向上、向下舍入、朝向零或远离零,以更简单的方式(即我们不关心舍入方向)。a
b
(a + b) / 2
结果是另一个寄存器长度有符号整数(很明显,平均值必须在寄存器长度有符号整数的范围内)。
执行此计算的最快方法是什么?
您可以选择两个整数最初将位于哪个寄存器中,以及平均值最终位于哪个寄存器中。
脚注 1:对于无符号整数,我们可以在两条指令中完成。这可能是最快的方法,尽管在英特尔 CPU 上,通过进位旋转超过 1 uop。但是当计数只有 1 时,只有一对。关于无符号均值的问答的答案讨论了效率。
add rdi, rsi
rcr rdi, 1
这两个数字以 和 开头,平均值以 结束。但是对于有符号的数字,将设置 CF,并将 a 旋转到符号位中。没有给出正确的答案。rdi
rsi
rdi
-1 + 3
1
+1
脚注 2:我指定了寄存器长度的有符号整数,这样我们就不能简单地用 or 指令对整数进行符号扩展。movsxd
cdqe
我最接近的解决方案使用四个指令,其中一个在 Intel 上是 3 个 uops,在 AMD Zen 上是 1 个 (https://uops.info/):rcr
add rdi, rsi
setge al
sub al, 1 # CF = !(ge) = !(SF==OF)
rcr rdi, 1 # shift CF into the top of (a+b)>>1
我认为一个更短的解决方案可能在于以某种方式组合中间两个指令,即执行 .CF ← SF ≠ OF
我已经看到这个问题,但这不是特定于 x86 的,而且似乎没有一个答案能像我的解决方案一样好。
答:
根据我们如何解释您的宽松四舍五入要求,以下情况可能是可以接受的:
sar rdi, 1
sar rsi, 1
adc rdi, rsi
这有效地将两个输入除以 2,将结果相加,如果为奇数,则再加 1。(请记住,根据移出的最后一个位来设置进位标志。rsi
sar
由于四舍五入到负无穷大,因此该算法的结果是:sar
如果 RDI、RSI 都是偶数或都是奇数,则完全正确
向下舍入(朝向负无穷大),如果 RDI 为奇数且 RSI 为偶数
如果 RDI 为偶数且 RSI 为奇数,则向上舍入(朝向正无穷大)
作为奖励,对于随机输入,平均舍入误差为零。
在典型的 CPU 上,它应该是 3 个 uops,延迟为 2 个周期,因为两者是独立的。sar
评论
avg(-3,-3)
-2 + -2 + CF(1)
-3
avg(3,3)
1 + 1 + CF(1)
lea eax, [rdi+rsi]
sar eax, 1
As an outside answer, consider the pavg
family of instructions.
I say "outside", since this is likely not acceptable to you. It assumes the value is unsigned 8-bit or 16-bit and in an SSE register, which of course also requires SSE. I mention it mainly since it is x86's anointed equivalent to averaging instructions in other ISAs.
In its defense, SSE is ubiquitous by now, even guaranteed on x86-64. Also, this instruction is 1 cycle, and actually can do 4 at once if you like. Best of all, unlike your original solutions, it also correctly handles overflow issues.
Note that it's possible to use an unsigned routine to implement a signed routine, though in general correctly accounting for overflow issues is a nightmare. Your current solution appears to already be broken in that regard, though.
评论
pxor
set1_epi8(0x80)
pavgb
pxor
pavgb
pavgw
setge
评论
rdi
rsi
add rdi, rsi
rdi
rcr rdi, 1
sar
cqo; add rax,rsi; adc rdx,0; shrd rax,rdx,1
cqo
mov hi, reg; sar hi, 63
mov
sar rdi, 1 ; sar rsi, 1 ; add rdi, rsi