x86汇编 - 4个给定数字中的2个最大值

时间:2016-10-24 23:36:37

标签: assembly x86

我在汇编程序中编写一个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          

3 个答案:

答案 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链接,请参阅标记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使用了带符号的比较。)

SSE4.1 PHMINPOSUW

我们可以为每个元素添加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;

嗨!