我在汇编程序中编写一个C子程序,需要找到传入的4个值中的2个最大值并将它们相乘。我正努力寻找最大的价值,但我有点卡住了。我有这个找到最大值,但我似乎无法推断如何获得第二高。任何建议将不胜感激
first:
push bp
mov bp,sp
mov ax,[bp+4]
mov [max1],ax
cmp ax,[bp+6]
jge skip1
mov ax,[bp+6]
mov max1,ax
skip1:
mov ax,max1
cmp ax,[bp+8]
jge skip2
mov ax,[bp+8]
mov max1,ax
skip2:
mov ax,max1
cmp ax,[bp+10]
jge mult
mult:
mul [max1],[max2]
jmp fin
fin:
pop bp
ret
end
答案 0 :(得分:2)
据推测,你并不是在寻找SIMD的答案,但我写这篇文章会很有趣。是的,SSE指令在16位模式下工作。 VEX编码的指令没有,因此您不能使用AVX 3操作数版本。幸运的是,我能够在没有任何额外的MOVDQA指令的情况下编写它,因此AVX无济于事。
IDK如何以您可能想要的方式回答这个问题,而不必为您做好功课。如果您真的对高性能实现感兴趣,而不仅仅是任何有效的实现,请更新您的问题。
由于您只需要返回两个最高数字的产品,您可以生产所有6个成对产品并采取最大值。 (4选择2 = 6)。
如果蛮力不起作用,你的使用不够:P
更新:我刚刚意识到,如果最大的成对产品来自两个负数,这将给出错误的答案。如果您可以排除负输入,或以其他方式排除输入,这是个问题。请参阅下面的SSE4.1版本,该版本分别找到max和2nd-max。
使用SSE2可以实现无分支的技巧。 (您可以仅使用SSE1在MMX寄存器中执行相同操作,SSE1添加了MMX寄存器版本的PMAXSW)。这只是11条指令(不包括序言/结语),and they're all fast, mostly single-uop on most CPUs。 (有关更多x86链接,请参阅x86标记wiki)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="kop">
<div class="name">John</div>
<div class="details"><a href="#">(details)</a> </div>
</div>
<div class="listt">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
<div class="kop">
<div class="name">John</div>
<div class="details"><a href="#">(details)</a> </div>
</div>
<div class="listt">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
如果您需要32位产品,PMAXSD是SSE4.1的一部分。也许用零(或PMOVZXWD)解包,并使用PMADDWD进行16b * 16b-> 32b向量乘法。奇数元素全为零,PMADDWD的水平加法部分只得到偶数元素中有符号乘法的结果。
有趣的事实:MOVD和;; untested, but it does assemble (with NASM)
BITS 16
;; We only evaluate 16-bit products, and use signed comparisons on them.
max_product_of_4_args:
push bp
mov bp, sp
; load all 4 args into a SIMD vector
movq xmm0, [bp+4] ;xmm0 = [ 0...0 d c b a ] (word elements)
pshuflw xmm1, xmm0, 0b10010011 ;xmm1 = [ 0.. c b a d ] (rotated left)
pshufd xmm2, xmm0, 0b11110001 ;xmm2 = [ 0.. b a d c ] (swapped)
pmullw xmm1, xmm0 ; [ 0.. cd bc ab ad ] (missing ac and bd)
pmullw xmm2, xmm0 ; [ 0.. bd ac bd ac ]
; then find the max word element between the bottom halves of xmm1 and xmm2
pmaxsw xmm1, xmm2
; now a horizontal max of xmm1
pshuflw xmm0, xmm1, 0b00001110 ; elements[1:0] = elements[3:2], rest don't care
pmaxsw xmm0, xmm1
pshuflw xmm1, xmm0, 0b00000001
pmaxsw xmm0, xmm1
; maximum product result in the low word of xmm0
movd eax, xmm0
; AX = the result. Top half of EAX = garbage. I'm assuming the caller only looks at a 16-bit return value.
; To clear the upper half of EAX, you could use this instead of MOVD:
;pextrw eax, xmm0, 0
; or sign extend AX into EAX with CWDE
fin:
pop bp
ret
end
在16位模式下不需要操作数大小的前缀来写入eax。 pextrw eax, xmm0, 0
前缀已经是所需编码的一部分。 66
无法组装(使用NASM)。
有趣的事实#2:pextrw ax, xmm0, 0
错误地将MOVQ负载反汇编为ndisasm -b16
:
movq xmm0, xmm10
为2 shuffle设计注释,2乘法。
$ nasm -fbin 16bit-SSE.asm
$ ndisasm -b16 16bit-SSE
...
00000003 F30F7E4604 movq xmm0,xmm10
...
$ objdump -b binary -Mintel -D -mi8086 16bit-SSE
...
3: f3 0f 7e 46 04 movq xmm0,QWORD PTR [bp+0x4]
...
我试着通过两次shuffle为它创建输入来尝试只做一个PMULLW。使用PSHUFB(掩码为16字节的常量)很容易。
但我试图将其限制为SSE2(也许是可以适应MMX的代码)。这是一个没有成功的想法。
[ d c b a ] ; orig
[ c b a d ] ; pshuflw
cd bc ab ad : missing ac and bd
[ b a d c ] ; pshuflw. (Using psrldq to shift in zeros would produce zero, but signed products can be < 0)
;; Actually, the max must be > 0, since two odd numbers will make a positive
我甚至不确定那会更好。需要额外的随机播放来获得水平最大值。 (如果我们的元素是无符号的,也许我们可以在0-vec上使用SSE4.1 PHMINPOSUW来一次性找到最大值,但是OP使用了带符号的比较。)
我们可以为每个元素添加32768,然后使用无符号的东西。
给定一个带符号的16位val:[ d d c c b b a a ] ; punpcklwd
[ b a b a b a d c ] ; pshufd
bd ad bc ac bb ab ad ac
: ab ac ad
: bc bd
: cd(missing)
: bb(problem)
将最低值映射到0,最高值映射到65535.(加,减,或XOR(无 - 无)是等效的。)< / p>
由于我们只有一条指令来找到水平最小值,我们可以用否定来反转该范围。我们需要先做 ,因为0保持0,而0xFFFF变为0x0001等。
因此,rangeshift = val + 1<<15
或-val + 1<<15
将我们的签名值映射为无符号值,使得最低无符号值是最大有符号值。要扭转这种情况:mapped = 1<<15 - val
。
然后我们可以使用PHMINPOSUW找到最低(无符号)字元素(最大原始元素),将其掩盖为全1,然后PHMINPOSUW再次找到第二低。
val = 1<<15 - mapped
这是更多说明。它可能不比使用整数寄存器的CMP / CMOV排序网络好。 (有关使用比较和交换的建议,请参阅@ Terje的评论。
答案 1 :(得分:1)
一个天真的初学者找到两个最大数字的方法(我希望这会让你在推理上失败,如何获得第二高......你只搜索第二高,同时寻找最高数字):
push bp
mov bp,sp
mov ax,[bp+4] ; temporary max1 = first argument
mov bx,8000h ; temporary max2 = INT16_MIN
; max2 <= max1
mov dx,[bp+6]
call updateMax1Max2
mov dx,[bp+8]
call updateMax1Max2
mov dx,[bp+10]
call updateMax1Max2
; ax and bx contains here max1 and max2
imul bx ; signed multiplication, all arguments are signed
; dx:ax = max1 * max2
; "mul" would produce wrong result for input data like -1, -2, -3, -4
pop bp
ret
updateMax1Max2:
; dx is new number, [ax, bx] are current [max1, max2] (max2 <= max1)
cmp bx,dx ; compare new value to lesser max2
jge updateMax1Max2_end
mov bx,dx ; new max2
cmp ax,dx ; compare new value to greater max1
jge updateMax1Max2_end ; new max2 is already <= max1
xchg ax,bx ; new value promoted to new max1, old max1 is now max2
updateMax1Max2_end:
ret
它同时保留两个临时最大值,为更复杂的更新的价格(不仅针对单个最大值测试新值,而且针对第二个测试新值)。
然后通过保持两个临时值按指定顺序进行优化,因此当新值低于max2时,会立即丢弃,而不是针对max1进行测试。
那个复杂的&#34;是比已经保留的max1 / max2&#34;更大的新值。代码被放入单独的子程序中,因此可以多次重复使用。
最后将[max1,max2]的初始状态设置为[first_argument,INT16_MIN],以便可以以简单的方式将剩余的三个参数应用于子例程(通过重用代码很多)。
Peter和Terje的建议提供了对高级可能性的深刻见解,但他们也很好地展示了性能编码如何变得棘手(因为他们都必须在其原始想法中添加勘误表)。
如果遇到困难或者有疑问,请尝试使用最简单的解决方案(就像你将其解决为人类一样)。只是尝试保持较低的指令数(以通用方式编写,尽可能在子例程中重用代码的任何更大部分),因此它易于调试和理解。
然后用几个可能的输入馈送它,同时运行极端情况([某些示例值],[INT16_MIN,INT16_MIN,INT16_MIN,INT16_MIN],[INT16_MAX,INT16_MAX,INT16_MAX,INT16_MAX],[-1,-2, - 3,-4],[ - 2,-1,0,INT16_MAX]等等,并验证结果是否正确(理想情况下也在某些代码中,因此您可以在下次更改例程后重新运行所有测试)。
这是至关重要的一步,它将使您远离原来错误的假设,忽略了一些极端情况结果。在理想情况下,甚至不直接运行代码,直接进入调试器并单步执行每个测试用例,不仅验证结果,还要检查计算过程中的内部状态是否按预期工作。
之后,您可以检查一些&#34;代码打包&#34;,如何利用该情况的所有属性来降低工作量(简化算法)和/或指令数量以及如何替换性能 - 用更快的方法来破坏代码。
答案 2 :(得分:1)
这是我的第一个处理四个16位无符号整数输入数之间的最大数( 80x86 +处理器兼容)的解决方案:
Procedure Max4; Assembler;
{ Input: AX, BX, CX, DX
Output: AX= MAX(AX,BX,CX,DX).
Temp: DI}
Asm
{ Input: AX, BX
Output: AX= MAX(AX,BX).
Temp: DI}
Sub AX,BX
CmC
SbB DI,DI
And AX,DI
Add AX,BX
{ Input: CX, DX
Output: CX= MAX(CX,DX).
Temp: DI}
Sub CX,DX
CmC
SbB DI,DI
And CX,DI
Add CX,DX
{ Input: AX, CX
Output: AX= MAX(AX,CX).
Temp: DI}
Sub AX,CX
CmC
SbB DI,DI
And AX,DI
Add AX,CX
End;
我的程序 Max4(),相当于 AX = Max4(AX,BX,CX,DX),有效 很棒的 AX = Max(AX,BX)子程序 返回两个数字之间的最大值 它被用了三次:
<强> AX =最大值(MAX(AX,BX),马克斯(CX,DX))强>
AX = Max(AX,BX)子程序的工作原理如下:
1) Diff=AX-BX.
2) If Diff>=0 then AX is the greatest number,
Output= Diff+BX= AX-BX+BX= AX.
3) If Diff<0 then BX is the greatest number,
must set Diff to 0,
Output= Diff+BX= 0+BX= BX.
在ASSEMBY:
{ Input: AX, BX
Output: AX= MAX(AX,BX).
Temp: DI}
Sub AX,BX
{Diff= AX-BX}
CmC
{If Diff>=0 -> FCarry=1 else FCarry=0}
SbB DI,DI
{If Diff>=0 -> DI=DI-DI-1==-1 else DI=DI-DI-0==0}
And AX,DI
{If Diff>=0 -> Diff=(Diff & -1)==Diff else Diff=(Diff & 0)==0}
Add AX,BX
{AX= Diff+BX}
但是这个解决方案仅适用于无符号16位数,只处理一个最大数(不进行乘法运算)。 下一个解决方案在 80x86 +处理器 上正常工作(使用有符号整数;处理两个最大数字):
Function Max42R(A,B,C,D:Integer):LongInt; Assembler;
Asm
Mov AX,A
Mov BX,B
Mov CX,C
Mov DX,D
{1ø swap (when needed), 1ø scan}
Cmp AX,BX
JLE @01
XChg AX,BX
{2ø swap (when needed), 1ø scan}
@01:Cmp BX,CX
JLE @02
XChg BX,CX
{3ø swap (when needed), 1ø scan}
@02:Cmp CX,DX
JLE @03
XChg CX,DX
{1ø swap (when needed), 2ø scan}
@03:Cmp AX,BX
JLE @04
XChg AX,BX
{2ø swap (when needed), 2ø scan}
@04:Cmp BX,CX
JLE @05
XChg BX,CX
{DX is the first greatest number;
CX is the second greatest number}
@05:Mov AX,DX
Mul CX
End;
冒泡排序算法的变体。 在 bubble-sort 中,你必须比较数组中的每对相邻数字 如果第一个大于第二个,则交换它们;如果发生交换,则重复阵列扫描,直到对阵列进行排序。 但在第一次扫描后,数组的最后一个值总是最大的数字。 假设四个输入值在虚拟数组中 ,我交换前三对寄存器,仅在需要时才能获得第一个最大价值,
在最后一个寄存器中 。之后,我只在需要时交换前两对寄存器,以获得第二大值,即倒数第二个 注册强>
Max4()程序可以写在80386 + 处理器,如下所示(支持32位有符号整数;处理一个最大数字) :
Function Max4I(A,B,C,D:Integer):Integer; Assembler;
{ Input: EAX, EBX, ESI, EDI
Output: EAX= MAX(EAX,EBX,ESI,EDI).
Temp: CX.
EAX EDX ECX are 1°, 2° AND 3° PARAMETERs.
Can freely modify the EAX, ECX, AND EDX REGISTERs. }
Asm
Push EBX
Push EDI
Push ESI
{------------------------}
Mov EAX,A
Mov EBX,B
Mov ESI,C
Mov EDI,D
{ Input: EAX, EBX
Output: EAX= MAX(EAX,EBX).
Temp: ECX}
Sub EAX,EBX
Mov ECX,0
SetL CL
Dec ECX
And EAX,ECX
Add EAX,EBX
{ Input: EAX, ESI
Output: EAX= MAX(EAX,ESI).
Temp: ECX}
Sub EAX,ESI
Mov ECX,0
SetL CL
Dec ECX
And EAX,ECX
Add EAX,ESI
{ Input: EAX, EDI
Output: EAX= MAX(EAX,EDI).
Temp: ECX}
Sub EAX,EDI
Mov ECX,0
SetL CL
Dec ECX
And EAX,ECX
Add EAX,EDI
{------------------------}
Pop ESI
Pop EDI
Pop EBX
End;
最后得到两个最大的最终解决方案 四个32位有符号整数(80386+个处理器)之间的数字。 它起Max42R()函数的作用:
Function Max42(A,B,C,D:Integer):Integer; Assembler;
{ Input: EAX, EBX, ESI, EDI
Output: EDI= 1° MAX(EAX,EBX,ESI,EDI).
ESI= 2° MAX(EAX,EBX,ESI,EDI).
Temp: ECX, EDX.
EAX EDX ECX are 1°, 2° AND 3° PARAMETERs.
Can freely modify the EAX, ECX, AND EDX REGISTERs. }
Asm
Push EBX
Push EDI
Push ESI
Mov EAX,A
Mov EBX,B
Mov ESI,C
Mov EDI,D
{ Input: EAX, EBX
Output: EAX= MIN(EAX,EBX).
EBX= MAX(EAX,EBX).
Temp: ECX, EDX}
Sub EAX,EBX
Mov EDX,EAX
Mov ECX,0
SetGE CL
Dec ECX
And EAX,ECX
Add EAX,EBX
Not ECX
And EDX,ECX
Add EBX,EDX
{ Input: EBX, ESI
Output: EBX= MIN(EBX,ESI).
ESI= MAX(EBX,ESI).
Temp: ECX, EDX}
Sub EBX,ESI
Mov EDX,EBX
Mov ECX,0
SetGE CL
Dec ECX
And EBX,ECX
Add EBX,ESI
Not ECX
And EDX,ECX
Add ESI,EDX
{ Input: ESI, EDI
Output: ESI= MIN(ESI,EDI).
EDI= MAX(ESI,EDI).
Temp: ECX, EDX}
Sub ESI,EDI
Mov EDX,ESI
Mov ECX,0
SetGE CL
Dec ECX
And ESI,ECX
Add ESI,EDI
Not ECX
And EDX,ECX
Add EDI,EDX
{ Input: EAX, EBX
Output: EAX= MIN(EAX,EBX).
EBX= MAX(EAX,EBX).
Temp: ECX, EDX}
Sub EAX,EBX
Mov EDX,EAX
Mov ECX,0
SetGE CL
Dec ECX
And EAX,ECX
Add EAX,EBX
Not ECX
And EDX,ECX
Add EBX,EDX
{ Input: EBX, ESI
Output: EBX= MIN(EBX,ESI).
ESI= MAX(EBX,ESI).
Temp: ECX, EDX}
Sub EBX,ESI
Mov EDX,EBX
Mov ECX,0
SetGE CL
Dec ECX
And EBX,ECX
Add EBX,ESI
Not ECX
And EDX,ECX
Add ESI,EDX
{EDI contain the first maximum number;
ESI contain the second maximum number}
Mov EAX,EDI
{------------------------}
Pop ESI
Pop EDI
Pop EBX
End;
如果第一个大于第二个,如何交换两个寄存器?
这是代码(在 80386 + 上):
{ Input: EAX, EBX
Output: EAX= MIN(EAX,EBX).
EBX= MAX(EAX,EBX).
Temp: ECX, EDX}
Sub EAX,EBX (* Diff= EAX-EBX; set Overflow flag and Sign flag *)
Mov EDX,EAX (* EDX= Diff; flags not altered *)
Mov ECX,0 (* ECX= 0; flags not altered *)
SetGE CL (* If Sign flag == Overflow flag ECX= 1 else ECX=0 *)
Dec ECX (* If Diff>=0, ECX=0 else ECX=-1 *)
And EAX,ECX (* If Diff>=0, EAX=(EAX & 0)=0 else EAX=(EAX & -1)=EAX *)
Add EAX,EBX (* EAX= Minimum value between input n. *)
Not ECX (* If Diff<0, ECX=0 else ECX=-1 *)
And EDX,ECX (* If Diff<0, EDX=(EDX & 0)=0 else EDX=(EDX & -1)=EDX *)
Add EBX,EDX (* EBX= Maximum value between input n. *)
功能 Max42 可以编写也可以作为80686+处理器的下一个代码, 只需要20个快速ASM寄存器&#39;说明强>:
Function Max42B(A,B,C,D:Integer):Integer; Assembler;
{ Input: EAX, EBX, ESI, EDI
Output: EDI= 1° MAX(EAX,EBX,ESI,EDI).
ESI= 2° MAX(EAX,EBX,ESI,EDI).
Temp: ECX.
EAX EDX ECX are 1°, 2° AND 3° PARAMETERs.
Can freely modify the EAX, ECX, AND EDX REGISTERs. }
Asm
Push EBX
Push EDI
Push ESI
Mov EAX,A
Mov EBX,B
Mov ESI,C
Mov EDI,D
{ Input: EAX, EBX
Output: EAX= MIN(EAX,EBX).
EBX= MAX(EAX,EBX).
Temp: ECX}
Mov ECX,EAX
Cmp EAX,EBX
CMovGE EAX,EBX
CMovGE EBX,ECX
{ Input: EBX, ESI
Output: EBX= MIN(EBX,ESI).
ESI= MAX(EBX,ESI).
Temp: ECX}
Mov ECX,EBX
Cmp EBX,ESI
CMovGE EBX,ESI
CMovGE ESI,ECX
{ Input: ESI, EDI
Output: ESI= MIN(ESI,EDI).
EDI= MAX(ESI,EDI).
Temp: ECX}
Mov ECX,ESI
Cmp ESI,EDI
CMovGE ESI,EDI
CMovGE EDI,ECX
{ Input: EAX, EBX
Output: EAX= MIN(EAX,EBX).
EBX= MAX(EAX,EBX).
Temp: ECX}
Mov ECX,EAX
Cmp EAX,EBX
CMovGE EAX,EBX
CMovGE EBX,ECX
{ Input: EBX, ESI
Output: EBX= MIN(EBX,ESI).
ESI= MAX(EBX,ESI).
Temp: ECX}
Mov ECX,EBX
Cmp EBX,ESI
CMovGE EBX,ESI
CMovGE ESI,ECX
{EDI contain the first maximum number;
ESI contain the second maximum number}
Mov EAX,EDI
{------------------------}
Pop ESI
Pop EDI
Pop EBX
End;
嗨!