寻找改进程序以确定排序数组的中位数

时间:2016-05-14 14:09:58

标签: assembly x86 masm masm32

函数参数是排序数组和数组的长度。目标是确定奇数或偶数长度数组的中位数。

通过确定精确的中间元素来处理奇数长度数组,甚至通过获得“跨越”中点并平均它们的两个元素来处理长度数组。

问题是:(在even_:标签之后)我不得不以你看到的方式重复确定跨骑值的左右两边。

mov eax, [edi+eax-4]行,我可以用不同的4的倍数来操纵它,并得到我想要的任何索引位置值。但是,如果我立即按mov eax, [edi+eax-4]mov esi, [edi+eax +/- any multiple of 4]说明,我总是得到“0”(尤其是任意选择)。

那么,我这样做的方式是最好的方式,还是我对如何一次访问两个数组元素缺乏智慧,可以这么说?

GetMedian   PROC
    push ebp
    mov ebp, esp
    mov eax, [ebp+12]           ; eax = length of array.
    mov ebx, 2
    cdq
    div ebx                     ; eax = Length of array/2.
    cmp edx,0
    je even_                    ; Jump to average the straddle.
    mov ebx, TYPE DWORD
    mul ebx                     ; eax now contains our target index.
    mov edi, [ebp+8]
    mov eax, [edi+eax]          ; Access array[eax].
    jmp toTheEnd
even_:
    mov ebx, TYPE DWORD
    mul ebx                     ; eax now contains our target index.
    mov edi, [ebp+8]            ; edi now contains @array[0].
    mov eax, [edi+eax-4]        ; Dereferences array[left] so a value is in eax.
    mov esi, eax                ; save eax (value left of straddle).
    mov eax, [ebp+12]           ; eax = length of array.
    mov ebx, 2
    cdq
    div ebx
    mov ebx, TYPE DWORD
    mul ebx                     ; eax now contains our target index.
    mov edi, [ebp+8]
    mov eax, [edi+eax]          ; Access array[right] (value right of straddle).
    add eax, esi                ; list[eax-1] + list[eax].
    mov ebx, 2
    cdq
    div ebx
toTheEnd:
    pop ebp
    ret 12
GetMedian ENDP

1 个答案:

答案 0 :(得分:2)

顺便说一句,您的代码实际上并不起作用:mov ebx, 2 clobbers ebx,但您不保存/恢复它。所以你已经踩到了一个在所有常见的ABI /调用约定中被调用保存的寄存器。请参阅代码wiki。

另外,我认为ret 12应该是ret 8,因为你需要两个4字节的args。 (见下文)。

这是一个有趣的想法:无分支,总是添加两个元素。对于奇数长度数组,它是相同的两个元素。对于一个偶数长度的阵列,它是中间向下和中间向上的。

如果您的代码实际上重复具有相同的数组长度,那么分支将很好地预测,条件分支可能会更好(在移位后test ecx, 1 / jnz oddjc )。 ESP。如果奇数长度是常见的情况。 有时值得无条件地做某事,即使并非总是需要它。

; Untested
GetMedian   PROC
    ;; return in eax.  clobbers: ecx, edx (which don't need to be saved/restored)
    mov   ecx, [esp+8]            ; ecx = unsigned len
    mov   edx, [esp+4]            ; edx = int *arr
    shr   ecx                     ; ecx = len/2.  CF = the bit shifted out. 0 means even, 1 means odd

    mov   eax, [edx + ecx*4]      ; eax = arr[len/2]
    sbb   ecx, -1                 ; ecx += 1 - CF.  
    add   eax, [edx + ecx*4]      ; eax += arr[len/2 + len&1]

    shr   eax, 1                  ; eax /= 2  (or sar for arithmetic shift)
    ret 12    ;;; Probably a bug
GetMedian ENDP
;; 5 instructions, plus loading args from the stack, and the ret.

我停止了制作堆栈帧的指令,因为这是一个不需要任何本地存储的叶子函数。使用ebp不会使回溯变得更容易或有帮助,并且浪费了指示。

对于大多数情况,您必须使用setcc根据标志在寄存器中获取0或1。但是CF很特别。 add-with-carry和sub-with-borrow使用它(我在这里利用),旋转进位指令也是如此。 adc reg, 0更为常见,但我需要反向,并根据CF提出sbb reg, -1添加0或1。

您确定ret 12是对吗?你的2个args只有8个字节。 <{1}}在弹出返回地址后向ret imm16 添加立即数,因此计数是由于esp / {{1而对堆栈指针的总更改对。

另外,我假设添加两个元素不会换行(进位或溢出),即使它是奇数长度数组的中间元素。

或者,另一种可能更糟的无分支方法

call

分支实施:

这可能更像是你从C编译器得到的东西,但是某些编译器可能不够聪明,无法根据shift设置CF。不过,我不会感到惊讶;我想我已经在轮班设置的标志上看到了gcc或clang分支。

ret

或者:

; Untested
; using cmov on two loads, instead of sbb to make the 2nd load address dependent on CF
GetMedian   PROC
    mov   ecx, [esp+8]            ; ecx = unsigned len
    mov   edx, [esp+4]            ; edx = int *arr
    shr   ecx, 1                  ; ecx = len/2.  CF = the bit shifted out. 0 means even, 1 means odd

    mov   eax, [edx + ecx*4]      ; eax = arr[len/2]
    mov   edx, [edx + ecx*4 + 4]  ; edx = arr[len/2+1]  (reads past the end if len=0, and potentially touches a different cache line than len/2)
    cmovc edx, eax                ; CF still set from shr.  edx = odd ? arr[len/2] : edx

    add   eax, edx
    shr   eax, 1                  ; eax /= 2  (or sar for arithmetic shift)
    ret 8
GetMedian ENDP

使用比例因子而非换档:

屏蔽; Untested GetMedian PROC ;; return in eax. clobbers: ecx, edx (which don't need to be saved/restored) mov ecx, [esp+8] ; ecx = unsigned len mov edx, [esp+4] ; edx = int *arr shr ecx ; ecx = len/2. CF = the bit shifted out. 0 means even, 1 means odd mov eax, [edx + ecx*4] ; eax = arr[len/2] jc @@odd ; conditionally skip the add and shift add eax, [edx + ecx*4 + 4] ; eax += arr[len/2 + 1] shr eax, 1 ; eax /= 2 (or sar for arithmetic shift) @@odd: ;; MASM local label, doesn't show up in the object file ret 8 GetMedian ENDP 的低位,然后使用 jnc @@even ret 8 ; fast-path for the odd case @@even: ;; MASM local label, doesn't show up in the object file add eax, [edx + ecx*4 + 4] ; eax += arr[len/2 + len&1] shr eax, 1 ; eax /= 2 (or sar for arithmetic shift) ret 8 ; duplicate whole epilogue here: any pop or whatever

这将依赖链从len缩短为一个arr[len/2] = [edx + (len/2)*4] = [edx + len*2],但这意味着第一次加载必须在分支之后。 (并且没有尾部重复(单独的len s),我们需要在某处实现无条件分支来实现shr结构而不是更简单的ret结构。)

if(odd){}else{}