x86 程序集 - 4 个给定数字中的 2 个最大值

Posted

技术标签:

【中文标题】x86 程序集 - 4 个给定数字中的 2 个最大值【英文标题】:x86 Assembly - 2 largest values out of 4 given numbers 【发布时间】:2016-10-24 23:36:36 【问题描述】:

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

【问题讨论】:

有很多方法,但它更多的是算法问题而不是 asm 问题。您可以对数字进行排序并选择前两个,或者编写一个查找数组中最大值的函数并应用两次,删除找到的那个。 mul [max1],[max2]:如果要使用两个显式操作数,则需要 imul。并且没有 x86 指令需要两个显式内存操作数。请参阅 x86 tag wiki 以获取指向 insn 集参考手册的链接。 比较前两个值,如果需要交换它们。然后比较最后两个值,并在需要时交换它们。最后,对中间的两个值做同样的事情。然后将前两个相乘。这可以通过寄存器中的所有四个值来完成。 @TerjeD.:哦,是的,那很好。我只是在考虑如何生产正确的产品而不关心最高与第二高的顺序。例如使用 SSE2,生成所有 6 个成对乘积,然后使用一些 PMAXSW 指令找到最大值。 @PeterCordes 我现在看到可能还需要最后交换第一个和最后一个(如果最大值是最后两个)。 【参考方案1】:

大概您不是在寻找 SIMD 答案,但我认为写起来会很有趣。是的,SSE 指令在 16 位模式下工作。 VEX 编码指令没有,因此您不能使用 AVX 3 操作数版本。幸运的是,我能够在没有任何额外的 MOVDQA 指令的情况下编写它,所以 AVX 没有帮助。

IDK 如何以您可能想要的方式回答这个问题,而不仅仅是为您做功课。如果您真的对高性能实现感兴趣,而不仅仅是任何可行的方法,请更新您的问题。


由于您只需要返回两个最大数字的乘积,因此您可以生成所有 6 个成对乘积并取最大值。 (4 选择 2 = 6)。

如果蛮力不起作用,则说明您使用的不够多:P

更新:我刚刚意识到,如果最大的成对积来自两个负数,这将给出错误的答案。如果您可以排除负输入,或者排除输入这是个问题。请参阅下面的 SSE4.1 版本,它分别找到最大值和 2nd-max。

这使用 SSE2 实现了无需分支的技巧。 (您可以只使用 SSE1 在 MMX 寄存器中做同样的事情,它添加了 PMAXSW 的 MMX 寄存器版本)。这只是 11 条指令(不包括序言/尾声),and they're all fast, mostly single-uop on most CPUs。 (有关更多 x86 链接,另请参阅x86 标签 wiki)

;; 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  

如果您想要 32 位产品,PMAXSD 是 SSE4.1 的一部分。也许用零(或 PMOVZXWD)解包,并使用 PMADDWD 进行 16b * 16b->32b 向量乘法。在奇数元素全为零的情况下,PMADDWD 的水平加法部分只是得到偶数元素的有符号乘法的结果。

有趣的事实:MOVD 和pextrw eax, xmm0, 0 在 16 位模式下写入 eax 不需要操作数大小的前缀。 66 前缀已经是所需编码的一部分。 pextrw ax, xmm0, 0 不组装(使用 NASM)。

有趣的事实 #2:ndisasm -b16 错误地将 MOVQ 负载反汇编为 movq xmm0, xmm10

$ 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]
...

2 shuffle,2 multiply 方式的设计说明。

[  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

我试图通过两次洗牌为它创建输入来尝试只做一个 PMULLW。使用 PSHUFB(使用 16 字节掩码常量)会很容易。

但我试图将其限制为 SSE2(也许代码可以适应 MMX)。这是一个没有成功的想法。

[  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 - vec 上使用 SSE4.1 PHMINPOSUW 一次性找到最大值,但 OP 使用有符号比较。)


SSE4.1 PHMINPOSUW

我们可以将 32768 添加到每个元素,然后使用未签名的东西。

给定一个带符号的 16 位 val:rangeshift = val + 1&lt;&lt;15 将最低映射到 0,最高映射到 65535。(加法、减法或 XOR(加法无进位)都等效于此。)

由于我们只有一条求水平最小值的指令,我们可以用否定来反转范围。我们需要这样做首先,因为 0 保持为 0,而 0xFFFF 变为 0x0001,等等。

所以-val + 1&lt;&lt;15mapped = 1&lt;&lt;15 - val 将我们的有符号值映射到无符号值,这样最低的无符号值就是最大的有符号值。要扭转这一点:val = 1&lt;&lt;15 - mapped

然后我们可以使用 PHMINPOSUW 找到最低(无符号)的单词元素(最大原始元素),将其屏蔽为全一,然后再次使用 PHMINPOSUW 找到第二低的。

push    bp
mov     bp, sp

pcmpeqw  xmm5, xmm5         ; xmm5 = all-ones (anything compares == itself)
psrlw    xmm5, 15           ; _mm_set1_epi16(1<<15)

movq     xmm0, [bp+4]
psubw    xmm5, xmm0         ; map the signed range to unsigned, in reverse order

phminposuw xmm1, xmm5       ; xmm1 = [ 0...  minidx  minval ]
movd     eax, xmm1          ; ax = minval

psrldq   xmm1, 2            ; xmm1 = [ 0...          minidx ]
psllw    xmm1, 4            ; xmm1 = [ 0...          minidx * 16 ]

pcmpeqw  xmm2, xmm6
psrlq    xmm2, 48           ; xmm2 = _mm_set1_epi64(0xFFFF)

psllq    xmm2, xmm1         ; xmm2 = _mm_set1_epi64(0xFFFF << (minidx*16))
; force the min element to 65535, so we can go again and get the 2nd min (which might be 65535, but we don't care what position it was in)
por      xmm2, xmm5

phminposuw xmm3, xmm2
movd     edx, xmm3          ; dx = 2nd min, upper half of edx=garbage (the index)

mov      cx, 1<<15          ; undo the range shift
neg      ax
add      ax, cx
sub      cx, dx

imul     cx                 ; signed multiply dx:ax = ax * cx
pop      bp
ret                         ; return 32-bit result in dx:ax (or caller can look at only the low 16 bits in ax)

这是更多说明。它可能并不比使用整数寄存器的 CMP/CMOV 排序网络好。 (有关使用什么比较和交换的建议,请参阅@Terje 的评论)。

【讨论】:

我正计划实施蛮力方法(感谢您的建议和详细回答) 最好的方法是:1.为每个产品定义 6 个常量 2.将 val1 移动到ax,将其乘以 val2 并存储在 product1 3.为每个产品执行此操作 4.比较每个产品。我对汇编编程非常陌生,所以这对我来说还不是一件容易的事。 @AaronHiller:我认为您的意思是“6 个存储位置”。如果你覆盖它们,它们就不是常量!但无论如何,不​​,这不是最好的方法。如果您要使用标量代码对其进行暴力破解,只需将当前的最大值保存在寄存器中,并在每次乘法后根据需要更新它。大概使用两个嵌套循环来做相当于for(int i=3 ; i&gt;=0 ;--i) for(int j=i-1 ; j&gt;=0 ; --j) prod=max(prod, args[i]*args[j]); 【参考方案2】:

一种天真的初学者找到两个最大数字的方法(我希望这会让你摆脱推理,如何获得第二高......你只需搜索第二高,同时搜索最高):

    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 进行测试。

复杂的“是比已经保存的max1/max2更大的新值”代码被放入单独的子程序中,因此可以重复使用多次。

最后 [max1,max2] 的初始状态设置为 [first_argument, INT16_MIN],这样子程序就可以以简单的方式应用于其余三个参数(通过重用代码很多)。


Peter 和 Terje 的建议提供了对高级可能性的深刻见解,但他们也很好地展示了性能 asm 编码是多么棘手(因为他们都必须在他们的原始想法中添加勘误表)。

当遇到困难或有疑问时,请尝试使用最直接的解决方案(就像您会像人类一样解决问题)。尽量减少指令数量(以通用方式编写,尽可能在子程序中重用大部分代码),以便调试和理解。

然后输入几个可能的输入,同时练习极端情况([一些示例值],[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], etc...),并验证结果是否正确(理想情况下也在某些代码中,因此您可以在下次更改例程后重新运行所有测试)。

这是关键的一步,它将使您摆脱最初的错误假设,忽略一些极端情况的结果。在理想情况下,甚至不要直接运行您的代码,直接进入调试器并单步执行每个测试用例,不仅要验证结果,还要继续检查计算过程中的内部状态是否按预期工作。

之后,您可能会检查一些“代码打高尔夫球”,如何利用这种情况的所有属性来降低工作量(简化算法)和/或指令数量,以及如何用更快的替代代码替换损害性能的代码接近。

【讨论】:

我在文本中使用“[pairs / quartets]”,在数学意义上使用带有方括号的 cmets 来处理值的集合(对/四重奏)。它与代码中用于读取内存内容的汇编方括号无关。【参考方案3】:

这是我处理四个 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(Max(AX,BX),Max(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.

在大会中:

 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;

它是冒泡排序算法变体。 在冒泡排序中,你必须比较数组中每一对相邻的数字 如果第一个大于第二个,则交换它们;如果发生交换,则重复数组扫描,直到对数组进行排序。 但在第一次扫描后,数组的最后一个值总是最大的数。 假设四个输入值在一个虚拟数组中,我只在需要时交换前三对寄存器,以获得第一大价值

那是在最后一个寄存器中。之后,我交换前两对寄存器,仅在需要时,得到第二大值,即倒数第二个 注册

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 寄存器的指令

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;

嗨!

【讨论】:

如果您解释了它的工作原理/原因,这将是一个更好的答案。它看起来相当有效。另请注意,max(4 inputs) 不是问题。问题是找到 2 个最大的输入(并返回它们的乘积)。这就是为什么我的答案形成 6 个成对产品并找到其中的最大值。 这个解决方案前请看回复0,谢谢。 当 2 个答案得分相同时,加载页面时它们的排序顺序是随机的。例如在我的屏幕上,您的新答案在此答案下方。 (如果你重新加载,它也可能会改变。)所以最好说“我的其他答案”或“@username 的答案”,而不是通过数字或位置(或特别是通过计票,因为那显然可以改变。) 我不认为有 2 个单独的答案很有用,因为它们并不是真正分开的。将一个答案的解释作为单独的答案发布是不行的。 edit他们成一个长答案。 (我已经发布了单独的答案,一个是针对 AVX2,另一个是针对 AVX512 并使用 AVX2 中不可用的指令。但这不是您在这里所做的。) 我创建了两个新的解决方案,我认为其中至少一个是有效的。核实!我重写了这篇文章。

以上是关于x86 程序集 - 4 个给定数字中的 2 个最大值的主要内容,如果未能解决你的问题,请参考以下文章

从给定的 x86 程序集编写 C 函数

不能被 K 整除的两个和的最大子集

众数问题:给定含有n各元素的多重集合S,每个元素在S中出现次数成为重数。多重集S中重数最大的元素成为众

剑指 Offer II 076. 数组中的第 k 大的数字

实例1.1 最大子列和问题(20 分)浙大版《数据结构(第2版)》题目集

最大数