考虑以下Intel 8086汇编程序:
CX保持非零值。
L: ADD AX, AX
ADC DX, 0
LOOP L
我被要求了解上面的代码并重写它以提高效率。 据我所知:
假设这是正确的,我认为一个更好的代码将SHL中的值,S1倍数。
SHL AX, CX
但是,我无法想出一种方法来对进程中的进位进行求和。 (或计算原始AX的CX最高有效位中的' 1'位数。)
非常感谢任何指导和帮助。
答案 0 :(得分:2)
您对当前代码如何工作的理解基本上是正确的。为了确保我们理解它,让我们逐步执行示例。通常情况下,这种事情可以通过手头的调试器完成,但我们真正需要的只是我们的头和一个可以显示二进制值的计算器。
让我们说AX
是55312(选择一个大的初始值可以让我们立即看到一个进位的效果)。 CX
将为4,当然DX
已预先归零。
55312 + 55312
溢出16位值的范围,因此进位位置位,AX
现在为45088.因为进位已设置,DX
= 1 。CX
减少到3。45088 + 45088
再次溢出,因此进位位置位,AX
现在为24640. DX
= 2; CX
= 2。24640 + 24640
没有溢出,因此进位位未设置,AX
现在为49280. DX
= 2; CX
= 1。49280 + 4928
溢出,因此进位位置位,AX
现在为33024. DX
= 3; CX
= 0。所以,当我们完成时,DX
是3.如果我们查看起始值的二进制表示:
1101 1000 0001 0000
↑ ↑
bit 15 bit 0
你可以看到你直觉的确认:这个值的高4(CX
)位中的set(1)位数是3,等于DX
。
这些类型的位级观察,导致了巧妙的,有点蠢蠢欲动的技巧,是大多数优化突破的关键,你已经通过思考你实际执行的代码已经发现了这个,这是非常好。
收集我们的想法,让我们明确地写出算法,假设AX
是输入值而CX
包含迭代次数:
CX
中的高AX
位,丢弃其余位。AX
。如果我们的目标是现代处理器 - 英特尔Nehalem,AMD巴塞罗那和更新 - 使用SHR
进行右移是一个简单的问题,然后使用POPCNT
指令来计算所需范围内的设定位数。例如:
; AX == input value
; CX == number of iterations
neg cx
add cx, 16 ; cx = 16 - cx
shr ax, cl ; ax = ax << cx
popcnt ax, ax ; ax = # of 1 bits in ax
这将是快速。没有分支/循环;只有4个简单的说明。您在执行时间内只查看少数几个周期,不可能出现分支错误预测。
但是,如果您的目标是POPCNT
指令不存在的旧CPU,该怎么办?好吧,你需要模仿它。实现种群计数/汉明加权算法有多种快速方法。在Pentium或更高版本中,最快的方式是:
; AX == input value
; CX == number of iterations
neg cx
add cx, 16 ; cx = 16 - cx
shr ax, cl ; ax = ax << cx
; emulate popcnt
mov dx, ax
shr dx, 1
and dx, 21845
sub ax, dx
mov cx, ax
and ax, 13107
shr cx, 2
and cx, 13107
add cx, ax
mov dx, cx
shr dx, 4
add dx, cx
and dx, 3855
mov ax, dx
shr ax, 8
add ax, dx
and ax, 63
这是this method的16位自适应,它使用一系列移位,加法和掩码来并行化位数。这些都是简单的指令,它仍然是无分支的,所以它在大多数处理器上都很快......但不是8088/8086!在那些古老的恐龙中,即使像这样的简单指令也需要多个周期来执行,更糟糕的是,它们都必须解码,因此慢速内存访问速度往往会减慢速度。如果您真的想为8088/8086优化此项,则需要使用查找表实现填充计数算法。而且,在这些处理器上,经常被遗忘的1字节XLAT
指令是查找表中值的最快方法:
LUT DB 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8
; AX == input value
; CX == number of iterations
neg cx
add cx, 16 ; cx = 16 - cx
shr ax, cl ; ax = ax << cx
; emulate popcnt using LUT
mov bx, OFFSET LUT
xlat ; equivalent to: mov al, [bx + al]
xchg al, ah
xlat ; equivalent to: mov al, [bx + al]
add al, ah
xor ah, ah
这需要256个字节来存储代码中的查找表(LUT),但是在8088/8086上执行速度绝对比执行所有算法更快。我们可以通过计算周期来估算执行速度:
neg cx ; 3 cycles
add cx, 16 ; 4 cycles
shr ax, cl ; 8+(4*CL) cycles
mov bx, OFFSET LUT ; 4 cycles
xlat ; 11 cycles
xchg al, ah ; 4 cycles
xlat ; 11 cycles
add al, ah ; 3 cycles
xor ah, ah ; 3 cycles
;-----------------
; 51+(4*CL) cycles
请注意,这里的慢速指令是右移。它需要一个固定的8个周期,加上每个移位的4个额外周期(即,移位计数,在CL
)。不幸的是,我们无法做到这一点。这意味着我们具有大约50个周期的最佳情况,最差情况下的性能仍然低于120个周期。
将其与原始代码进行比较:
xor dx, dx ; 3 cycles
L: add ax, ax ; 3 cycles
adc dx, 0 ; 4 cycles
loop L ; taken: 17 cycles; not-taken: 5 cycles
;---------------------------------------
; 8+(24*CL) cycles
这里,大致的周期数取决于CX
(循环计数),因为它决定了分支的采用次数。因此,在最好的情况下,此代码大约需要32个周期;在最坏的情况下,它需要400个周期。
我想重申,即使在像8086这样的简单芯片上,循环计数也不准确,但它确实为我们提供了一种估算性能的合理方法。您的原始代码确实具有稍好的最佳性能(在CX
较小的情况下),但我们优化的,基于位计数,基于LUT的方法具有更好的最坏情况性能,更重要的是,更好地扩展。您可以在以下两种方法的图形比较中清楚地看到这一点:
只要CX
很小,您的原始代码就是合理的实现。但是随着CX
越来越大,例程会因为所有LOOP
而变得越来越慢。指数越来越慢。我们基于LUT的方法具有更大的开销(并且甚至不计算LUT添加到二进制文件中的膨胀),但真正开始得到回报,因为CX
获得大。 总之,我们已经为执行速度增加了代码大小,这是一种常见的优化权衡。
现在,我需要干净。我一直偷偷地假设CX
永远不会超过16,如果CX
大于16,那么所有&#34;优化&#34;我一直在向您展示的代码无法正常工作,因为SHR
指令会尝试移出太多位。如果您需要处理CX
&gt; 16,然后你需要调整代码,使它钳位 CX
小于或等于16。这意味着要么条件分支还是一系列聪明的位-twiddling指令,其中任何一个都会增加代码的复杂性并增加其循环次数。换句话说,这将增加&#34;优化&#34;的基线开销。方法,但这种方法将比原始方法更好地 scale 。从图形上看,红线将向上翻转。
(您的原始代码不需要进行任何修改 - 它会处理高达65,535的CX
值而不会带来任何额外的惩罚,因为它只会保留LOOP
。但是正如我们所做的那样已经看到,每个LOOP
都会有显着的性能损失。)
&#34;调整&#34;代码看起来像这样:
LUT DB 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8
; AX == input value
; CX == number of iterations
mov bx, 16 ; 4 cycles
cmp cx, bx ; cx < 16? ; 3 cycles
jae SkipShift ; 4 cycles (fall-through); 16 cycles (branch)
sub bx, cx ; 3 cycles
mov cx, bx ; cx -= 16 ; 2 cycles
shr ax, cl ; ax <<= cx ; 8+(4*CL) cycles
SkipShift:
mov bx, OFFSET LUT ; 4 cycles
xlat ; 11 cycles
xchg al, ah ; 4 cycles
xlat ; 11 cycles
add al, ah ; 3 cycles
xor ah, ah ; 3 cycles
如果采用此JAE
,您将支付16个周期的罚款,但在这种情况下我们能够跳过减法和转移,这将弥补这些丢失的周期。如果没有采用JAE
,执行就会失败,我们只会失去4个周期。总体而言,最佳情况下的性能约为60个周期,而最坏情况下的性能约为两倍。
答案 1 :(得分:0)
使用非零cl假设,该指令对将是等效的:
shld dx,ax,cl
shl ax,cl
答案 2 :(得分:0)
如果您没有Dim price As Decimal
If Decimal.TryParse(memberprices.RentalPrice, price) Then
Dim strPrice As String = price.ToString("F2")
' .. use "strPrice" somehow ...
Debug.Print(strPrice)
End If
(例如,因为您的代码必须在80286上运行,或者您想在非x86 CPU上使用相同的代码),您可以执行以下伪代码:
shld
答案 3 :(得分:0)
在其他答案中发布任何变化后,将移位的AX的高位置于DX中,您需要DX的一些变体(计算1位数)。执行此操作的代码有点冗长,下面显示了32位模式的示例。对于真正的8086,最好使用256字节表,将索引转换为索引中的1位数,其序列如下:
xor bx,bx
mov bl,dl ;bl = lower bits
mov dl,table[bx] ;dl = lower # bits set
mov bl,dh ;bl = upper bits
add dl,table[bx] ;dl = total # bits set
; ...
xor dh,dh ;optional, clear upper bits dx
edi示例代码的32位popcnt:
mov edx,edi ;edx = edi
shr edx,1 ;mov upr 2 bit field bits to lwr
and edx,055555555h ; and mask them
sub edi,edx ;edi = 2 bit field counts
; 0->0, 1->1, 2->1, 3->1
mov eax,edi
shr edi,02h ;mov upr 2 bit field counts to lwr
and eax,033333333h ;eax = lwr 2 bit field counts
and edi,033333333h ;edx = upr 2 bit field counts
add edi,eax ;edi = 4 bit field counts
mov eax,edi
shr eax,04h ;mov upr 4 bit field counts to lwr
add eax,edi ;eax = 8 bit field counts
and eax,00f0f0f0fh ; after the and
imul eax,eax,01010101h ;eax bit 24->28 = bit count
shr eax,018h ;eax bit 0->4 = bit count