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<<15
将最低映射到 0,最高映射到 65535。(加法、减法或 XOR(加法无进位)都等效于此。)
由于我们只有一条求水平最小值的指令,我们可以用否定来反转范围。我们需要这样做首先,因为 0 保持为 0,而 0xFFFF 变为 0x0001,等等。
所以-val + 1<<15
或mapped = 1<<15 - val
将我们的有符号值映射到无符号值,这样最低的无符号值就是最大的有符号值。要扭转这一点:val = 1<<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>=0 ;--i) for(int j=i-1 ; j>=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 个最大值的主要内容,如果未能解决你的问题,请参考以下文章
众数问题:给定含有n各元素的多重集合S,每个元素在S中出现次数成为重数。多重集S中重数最大的元素成为众