还有什么办法解决
总是有很多方法可以做任何事情。有些会比其他的更有效,并且有不同的效率衡量标准。不同的效率测量包括代码大小(以指令字节为单位)或小型阵列或大型阵列的性能。对于真正的 8086,代码大小通常是性能的决定因素,但对于现代 x86 CPU,这绝对不是真的。 (请参阅 x86 标签 wiki 以获取文档链接)。
没有必要在内存中存储 10;它应该是一个equ 常量。 IDK 如果您应该假装您正在编写一个不利用所有汇编时常量的函数。如果是这样,那么请注意如何使用常量。 (就像不要写 mov di
n + OFFSET a 来在汇编时计算结束指针。)
您可以避免the slow loop instruction,而无需增加循环中的指令数,方法是从数组末尾向下计数索引,并使用索引寻址模式。
另外,由于您的数组是相邻的,您可以只使用一个从 a 开始到 b 结束的循环
mov bx, OFFSET a ; no point in using LEA for this
mov si, length_ab - 1 ; index of the last element
xor ax,ax
sum_loop: ; do {
add al, [bx+si]
dec si
jg sum_loop ; } while(si > 0)
jmp print_num ; tailcall optimization: print_num will return directly to our caller
;call print_num
;ret
section .rodata
a: db 1,3,5,7,9,11,13,15,17,18
b: db 0,2,4,6,8,10,12,14,16,19
end_b: ; put a label after the end of b
length_ab equ $ - a ; this is NASM syntax, IDK if emu8086 accepts it
n equ 10
或者利用a 是静态的:add AL, [a + SI]。这在真正的 8086 上可能会更慢,因为它在循环中放置了额外的 2 字节代码,8086 每次都必须重新获取。在现代 CPU 上,保存 mov bx, OFFSET a 指令对于总代码大小是值得的。 (如果你在一个循环中多次使用同一个指针,那么将它放在寄存器中是有意义的。)
如果您知道您的总和不会溢出一个字节,您可以与 add ax, [si] 并行处理 2 个字节,最后是 add al, ah。但这绝对是一种特殊情况,处理一般情况(避免进位到下一个字节)with SWAR techniques 仅适用于 2 字节字。在 386 或更高版本的 16 位代码中,您可以使用 32 位寄存器并分别屏蔽奇数和偶数字节。
在某些超标量 CPU(如 Intel pre-Sandybridge,每个时钟周期只能执行一次加载)上,这会更快,允许您在每个时钟添加近 2 个字节:
xor ax,ax
xor dx,dx
sum_loop: ; do{
mov cx, [si]
add al, cl
add dl, ch
add si, 2
cmp si, end_a
jb sum_loop ; } while (si < end_pointer)
add al, dl
;; mov ah,0 ; if necessary
但在其他 CPU 上,最好只展开并使用 add al, [si] / add dl, [si+1] 而不是使用单独的加载指令。
在 Intel P6 和 Sandybridge 系列以外的 CPU 上,al 和 ah 不会单独重命名,因此 add ah, ch 会对完整的 ax 寄存器产生错误的依赖性。这就是为什么我使用dl 而不是ah。
请注意,至少在现代 Intel CPU (Haswell/Skylake) 上,xor ax,ax 不会破坏依赖关系。它使 AX 为零,但它不会消除对 EAX 旧值的无序执行数据依赖性。见How exactly do partial registers on Haswell/Skylake perform? Writing AL seems to have a false dependency on RAX, and AH is inconsistent。它在 Sandybridge 和更早的版本上可能会破坏深度,但绝对更喜欢 xor eax,eax 来清零寄存器。
如果您不需要您的代码与过时的 8086 兼容,您可以使用 SSE2 psadbw 只需几个步骤即可完成整个工作。
请参阅Sum reduction of unsigned bytes without overflow, using SSE2 on Intel 了解说明。
您的两个数组共有 20 个字节,因此我们可以对其进行硬编码并将其处理为 16 + 4。
pxor xmm0, xmm0 ; xmm0 = 0
movd xmm1, [a+16] ; load last 4 bytes
psadbw xmm1, xmm0 ; sum2 = xmm1 = |b[7]-0| + |b[8]-0] + ...
psadbw xmm0, [a] ; horizontal sum 16 bytes into 2 partial sums in the two 64-bit halves (sum0 and sum1)
; then combine those three 16-bit sums into a single sum.
paddw xmm1, xmm0 ; sum2 += sum0
punpckhqdq xmm0, xmm0 ; get the high half of xmm0
paddw xmm1, xmm0 ; sum2 += sum1
movd eax, xmm1
movzx eax, al ; truncate the sum to 8-bit
jmp print_num
section .rodata
ALIGN 16 ; having a aligned lets us use [a] as a memory operand, or movdqa
a: ...
b: ...
是的,这将在 16 位模式下组装(例如使用 NASM)。
稍后截断而不是在每一步之后截断对于加法是很好的,因为从低字节回绕或进位是一样的。
如果您不能利用 a 和 b 相邻的优势,您可以:
movdqu xmm0, [a]
movdqu xmm1, [b]
paddb xmm0, xmm1 ; add packed bytes (no carry across byte boundaries)
psrldq xmm0, 6 ; shift out the high 6 bytes from past the end of a and b
甚至避免读取超出数组末尾的内容:
movq xmm0, [a]
pinsrw xmm0, [a+8], 4
我刚刚意识到,由于您显然确实希望将总和包装为 8 位,您可以使用 paddb 来提高效率。对于大数组,你可以用paddb累加,最后做一个psadbw。
movd xmm1, [a+16] ; load last 4 bytes, zeroing the rest of the register
paddb xmm1, [a]
pxor xmm0, xmm0 ; xmm0 = 0
psadbw xmm1, xmm0 ; horizontal sum one vector of byte-sums
movhlps xmm0, xmm1 ; extract high half into a different register
paddw xmm0, xmm1
movd eax, xmm1
movzx eax, al ; truncate the sum to 8-bit
jmp print_num