函数参数是排序数组和数组的长度。目标是确定奇数或偶数长度数组的中位数。
通过确定精确的中间元素来处理奇数长度数组,甚至通过获得“跨越”中点并平均它们的两个元素来处理长度数组。
问题是:(在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
答案 0 :(得分:2)
mov ebx, 2
clobbers ebx,但您不保存/恢复它。所以你已经踩到了一个在所有常见的ABI /调用约定中被调用保存的寄存器。请参阅x86代码wiki。
另外,我认为ret 12
应该是ret 8
,因为你需要两个4字节的args。 (见下文)。
这是一个有趣的想法:无分支,总是添加两个元素。对于奇数长度数组,它是相同的两个元素。对于一个偶数长度的阵列,它是中间向下和中间向上的。
如果您的代码实际上重复具有相同的数组长度,那么分支将很好地预测,条件分支可能会更好(在移位后test ecx, 1
/ jnz odd
或jc
)。 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{}