【问题标题】:Assembly program that finds second largest value in array在数组中找到第二大值的汇编程序
【发布时间】:2017-11-24 03:52:12
【问题描述】:

我写了一个汇编程序来查找数组中的最大值,但现在我希望它找到数组中的第二大数。我将如何修改我的程序来做到这一点?

这是我编写的程序,它确实有效。程序打印数组中的所有值,然后找到数组的最大值。现在我希望它找到第二大的值。

 %include "io.mac"
.STACK 100H 

.DATA
   Numbers DW 3,4,5,2,6,0
   msg0  db "Printing values in array",0
   msg1  db "Max",0
   msg2  db "Min",0

   .CODE
        .STARTUP
    PutStr msg0
    mov dx, Numbers 
    mov si, dx ;point to array
    printValues:
    cmp word [si], 0
    je procedure
    nwln
    PutInt [si]
    add si, 2
    jmp printValues

    procedure:
    push Numbers ;push Number to stack to pass parameter by stack
    call maxMeth
    nwln
    PutStr msg1
    nwln

    PutInt ax
    nwln




    complete:
.EXIT





maxMeth:
    enter 0,0 ;save old bp and set bp to sp
    mov si, [bp+4] ;point to array 
    mov ax, [si]   ; ax holds max
    add si,2 ; Increment si to next number

;Now entering loop
max:   
    cmp word [si],0   ; checks to see if the number is 0 and if it is, then we are done.
    je finish
    cmp ax, [si]        ; ax holds the max . So if ax is greater than si, then dont assign si to ax. 
    jge increment
    jmp newMax
newMax: 
    mov ax, [si] ; Otherwise we have a new a max

increment:   
    add si, 2   ;now increment si to check the next value and jump back to the main loop.
    jmp max

finish: ;clean up.
    leave ;pop bp
    ret   ;return

我编辑了我的程序以跟踪第二个最大值,我收到的第二个最大值的结果是 3。我希望程序输出 5。我不知道为什么我得到错误的输出。这是我对程序的修改。

%include "io.mac"
.STACK 100H 

.DATA
   Numbers DW 3,4,5,2,6,0
   msg0  db "Printing values in array",0
   msg1  db "Max",0


   .CODE
        .STARTUP
    PutStr msg0
    mov dx, Numbers 
    mov si, dx ;point to array
    printValues:
    cmp word [si], 0
    je procedure
    nwln
    PutInt [si]
    add si, 2
    jmp printValues

    procedure:
    push Numbers ;push Number to stack to pass parameter by stack
    call maxMeth
    nwln
    PutStr msg1
    nwln

    PutInt ax
    nwln

   PutInt bx
    nwln


    complete:
.EXIT





maxMeth:
    enter 0,0 ;save old bp and set bp to sp
    mov si, [bp+4] ;point to array 
    mov ax, [si]   ; ax holds max
    mov bx, [si]   ; ax holds max
    add si,2 ; Increment si to next number

;Now entering loop
max:   
    cmp word [si],0   ; checks to see if the number is 0 and if it is, then we are done.
    je finish          ;equals 0, then finished
    cmp ax, [si]         
    jg testSecondMax   ;So if ax is greater than si, then dont assign si to ax and check second max
    jmp newMax         ;otherwise go to new max


newMax: 
    mov ax, [si] ; save new max
    jmp increment ; and increment

testSecondMax:
    cmp bx, [si]
    jl secondMax
    jg increment

secondMax:
    mov bx, [si]
    jmp increment

increment:  
    add si, 2   ;now increment si to check the next value and jump back to the main loop.
    jmp max 



finish: ;clean up.
    leave ;pop bp
    ret   ;return

【问题讨论】:

  • 通常的方法是在遍历数组时跟踪最大值和第二最大值。与 2nd-max 进行比较,如果更大,则插入有效的 2 项排序列表(您应该将其保存在寄存器中)。
  • 运行两次“查找最大”:运行一次,将列表中的所有匹配项归零,然后再次运行

标签: assembly nasm x86-16


【解决方案1】:

我最终为此编写了代码,因为它让我想知道我可以如何有效地实现循环。如果你想自己解决你的作业,不要看我的代码,只看英文的要点。使用调试器单步执行您的代码。


会这样改变你的代码:

  • NASM 风格:在函数中使用本地标签(如.noswap:)。将操作数缩进到一致的列,这样它看起来就不会参差不齐。用输入/返回值和调用约定(注册它的内容)评论您的函数。

  • newMax: 之前优化jmp next_instruction,因为它只是一个昂贵的无操作跳转到执行将失败的地方。

  • 除非可能针对真正的 8086 进行优化,否则不要使用 enter,它很慢。

  • 将要检查的每个元素加载到寄存器中,而不是多次使用相同的内存操作数。 (x86-16 有 6 个整数寄存器,除了 BP/SP;使用它们。)

  • 将循环退出条件分支放在底部。 (如果需要,跳转到循环入口点)。

  • 在两个寄存器中保存一个最大值和第二个最大值,就像在 AX 中保存最大值一样。

  • 当您找到大于 2nd-max 的元素时,保留您拥有的 3 个数字中最大的 2 个。即在 2 个寄存器中维护一个 2 元素队列/排序列表。

未经测试:

; word max2Meth(word *array);
; Input: implicit-length array (terminated by a 0 element),
;        pointed to by pointer passed on the stack.  (DS segment)
; returns in ax
; clobbers: cx, dx
global max2Meth
max2Meth:
    push  bp
    mov   bp, sp     ; make a stack frame.  (ENTER is slow, don't use it)
    push  si         ; SI is call-preserved in many calling conventions.  Omit this if you want to just clobber it.

    mov   si, [bp+4] ; pointer to array 

    mov   ax, [si]   ; assume that the list is non-empty
    mov   dx, ax     ; start with max = max2 instead of putting a conditional xchg outside the loop

    jmp   .loop_entry   ; enter at the bottom, at the conditional branch
;;; ax: 2nd max
;;; dx: max

.maxloop:              ; do {
    cmp cx, ax         ; check against 2nd-max, because the common case is less than both.
    jg  .updateMaxes   ; optimize for the common case: fall through on not-found

.loop_entry:
    add  si, 2
    mov  cx, [si]      ;   c = *p++;
    test cx, cx
    jnz .maxloop       ; } while (c != 0);

.finish:
   ; 2nd-max is already in AX, just clean up and return

    pop  si
    leave    ;pop bp would be faster because SP is already pointing to the right place
    ret

; This block is part of the function, even though it's placed after the ret
.updateMaxes:
    mov  ax, cx           ; Bump the old 2nd-max unconditionally
    cmp  ax, dx
    jle  .loop_entry      ; if 2nd_max <= max, return to the loop
    xchg ax, dx           ; otherwise swap
    jmp  .loop_entry

将块用于罕见情况下是很好的,因为这意味着常见情况可以直接通过而没有采取分支。通常将 if/else 条件内联需要 @ 987654328@ 某处只是为了避免在 if 之后运行 else 部分。但是.updateMaxes: 最终变得相当优雅,IMO:它必须跳回循环,我们可以在交换之前或之后这样做。

16-bit xchg is 3 uops(与 3 个 mov 指令一样昂贵),但为了在 new-max 情况下更便宜(并且只做mov ax, dx / mov dx, cx),我们必须制作 new-2ndmax情况较慢。假设它更有可能只更新第二个最大值而不是同时更新两者,我认为 mov 和 cmp/jcc 在那里是一个胜利。您可以使用cmov(在 686 CPU 上)使该部分无分支,这可能很好。使整个事情无分支会给你一个相当长的依赖链,并且不值得,除非数组元素平均在最后变得更大,所以你一直有频繁的最大更新(但不是模式,所以你获取分支未命中)。

在 Intel Haswell/Skylake 上,内部循环只有 4 个融合域微指令(比较/分支都可以进行宏融合)。在长时间没有更新的情况下,它应该以每时钟 1 次迭代运行。

如果您正在优化代码大小而不是速度(例如,对于真正的 8086),请使用 ax 作为临时地址并使用 lodsw 而不是 mov ax, [si]add si, 2。 (将您的 max 保存在不同的寄存器中)。

使用隐式长度列表,您不能只在 ax 中使用带有 2nd-max 的 scasw,因为您需要检查 0 以及 > 2nd-max :/

作为进一步的优化,如果您使用的是微型模型 (SS = DS),您可以使用 bp 而不是 si,因为您不需要在加载指针后访问堆栈。你可以只用pop bp 而不是leave


在我想到简单地使用 ax = dx = first element 进入循环之前,我打算在循环之前使用这段代码:

    mov   ax, [si]   ; assume that the list is non-empty
    mov   dx, [si+2] ; but check if it's only 1 element long, like maxMeth does
    test  dx, dx
    jz    .finish

    add   si, 4      ; first 2 elements are loaded

    cmp   ax, dx     ; sort them so ax <= dx
    jng  .noswap
    xchg  ax, dx
 .noswap:

另一种构建内部循环的方式如下:

.maxloop:              ; do {
    add  si, 2
    mov  cx, [si]      ;   c = *p++;

    test cx, cx
    jz .finish         ; jz instead of jnz: exit the loop on the sentinel value

    cmp cx, ax         ; check against 2nd-max, because the common case is less than both.
    jng .maxloop

;; .updateMaxes:  ;; conditionally fall through into this part 
    mov  ax, cx           ; Bump the old 2nd-max unconditionally
    cmp  ax, dx
    jle  .maxloop         ; if 2nd_max <= max, return to the loop
    xchg ax, dx           ; otherwise swap
    jmp  .maxloop

.finish:

这可能会更好,因为我们可以从循环的顶部开始。我们使用跳过条件更新内容的jz 退出循环,因此我们没有任何仅用于跳过“妨碍”代码的分支。 (即我们已经成功地有效地布置了我们的代码块。)

某些 CPU 的唯一缺点是 test/jz 和 cmp/jg 是背靠背的。当条件分支被多条指令分隔时,一些 CPU 会做得更好。 (例如,除非你幸运地知道这些是如何击中 Sandybridge 上的解码器的,否则两个分支中的一个不会进行宏融合。但它们会在第一个循环中。)


提醒:Stack Overflow 用户的贡献在 cc by-sa 3.0 下获得许可,需要注明出处,所以如果你复制粘贴我的整个代码,请确保在评论中包含 https://stackoverflow.com/questions/47466116/assembly-program-that-finds-second-largest-value-in-array/47467682#47467682

【讨论】:

  • 当人们没有诚信支持他们的投票时,这令人沮丧。 (我当然没有看到任何可以被认为是优越的竞争答案......)
  • 通过一些展开,您可以接近基于分支的方法在每个周期检查 2 个值的限制(每个周期限制为 2 个分支),对于不经常更新的情况,这几乎是两倍的速度.除此之外,我只能想到适用于某些类型分布的方法:如果长期第二大值通常至少是典型值的两倍(对于大多数分布来说都是如此,统一是一个明显的例外)你可以@ 987654350@ N 个值一起从内存中,然后检查是否大于当前的第二大值。
  • 糟糕,我没有注意到它是基于哨兵的循环,也没有被签名。如果您必须 support 负值,则可以解决签名问题,但在实践中不太可能遇到它们,只需对 or-accumulated 值进行无符号比较以进行乐观检查。任何负值都会导致它落入慢速路径,但你会在那里进行签名比较,因此它保持安全。哨兵杀了它。
  • @BeeOnRope:是的,未签名的好点。不过,只有 SIMD 可以解决哨兵问题。 (或者 SWAR bithacks 用于检测零字,我猜,如果你有 64 位寄存器但没有 SIMD,就像在某些 RISC CPU 上一样,也许值得这样做。)
  • 我过去用来解决“什么是分布???”的一种方法问题是在运行时动态选择不同的循环。通过实现在不同循环之间进行选择的隐式状态机,您基本上可以在汇编级别“免费”执行此操作(并且在 C 或 C++ 中使用goto 进行更丑陋的程度)。例如,您有为正常情况编写的循环,其中“发现更大”是不寻常的,但是当您陷入updateMaxes 时,您不会跳回该循环,而是说循环的一个展开迭代,并且如果你发现再次...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-12
  • 2021-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多