两个数组的总和,每个n字节

时间:2017-08-26 22:08:43

标签: assembly emu8086 x86-16

这是我的解决方案我想知道它是否正确以及解决问题的另一种方法

include 'emu8086.inc'
org 100h

mov CX,n
lea SI,a
mov AL,0

start_sum_a:         ;sum all the n elements of the first array
add AL, [SI] 
inc SI
loop start_sum_a

mov CX,n
lea SI,b 

start_sum_b:        ;sum all the n elements of the 2nd array to 
add AL, [SI]        ;the first sum
inc SI
loop start_sum_b

call print_num      ;print the sum

ret

a db 1,3,5,7,9,11,13,15,17,18
b db 0,2,4,6,8,10,12,14,16,19
n dw 10

DEFINE_PRINT_NUM
DEFINE_PRINT_NUM_UNS

2 个答案:

答案 0 :(得分:2)

  

我想知道它是否正确

你的解决方案看起来还不错,虽然我预感到“emu8086.inc”中定义的函数 print_num 会更期望AX寄存器中的数字。因此,最好将mov AL,0指令更改为xor ax,ax,这将清除整个AX寄存器,而不仅仅是低字节AL

  

解决问题的另一种方法是什么

如果为两个数组设置单独的指针,您可以选择在单个循环中完成工作。

    lea  si, a
    lea  di, b
    mov  cx, n
    xor  ax, ax
start_sum:
    add  al, [si]        ;Element of a array
    add  al, [di]        ;Element of b array
    inc  si
    inc  di
    loop start_sum

但是由于这些数组的起点在内存中相距一定距离(10),因此只使用1个指针的解决方案:

    lea  si, a
    mov  cx, n
    xor  ax, ax
start_sum:
    add  al, [si]        ;Element of a array
    add  al, [si + 10]   ;Element of b array
    inc  si
    loop start_sum

最后,由于这些数组在内存中相邻,因此循环可以更简单。只需将迭代次数加倍(Peter Cordes之一的建议之一):

    lea  si, a
    mov  cx, n
    shl  cx, 1           ;Double the counter
    xor  ax, ax
start_sum:
    add  al, [si]        ;Element of a array or b array
    inc  si
    loop start_sum

答案 1 :(得分:2)

  

解决问题的另一种方法是什么

总有很多方法可以做任何事情。有些将比其他更有效,并且有不同的效率衡量标准。不同的效率测量包括代码大小(指令字节),或小型阵列或大型阵列的性能。对于真正的8086,代码大小通常是性能的决定因素,但对于现代x86 CPU来说,这绝对不是真的。 (请参阅标记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]并行,并在末尾add al, ah执行2个字节。但这绝对是一个特殊情况,并且处理一般情况(避免进入下一个字节)with SWAR techniques不能仅使用2字节字。在386或更新版本的16位代码中,您可以使用32位寄存器并分别屏蔽奇数和偶数字节。

在一些超标量CPU(如英特尔预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]而不是使用单独的加载指令。

在英特尔P6和Sandybridge系列以外的CPU上,alah未单独重命名,因此add ah, ch将对完整{{1}具有错误依赖关系注册。这就是我使用ax代替dl的原因。

请注意,ah至少在现代英特尔CPU(Haswell / Skylake)上并非依赖性。它确实为零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。

xor ax,ax

是的,这将以16位模式组装(例如NASM)。

稍后截断而不是在每一步之后都可以添加,因为从低位字节开始或执行是同样的事情。

如果您无法利用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: ... 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

我刚刚意识到,因为你显然希望将总和包裹到8位,你可以使用movq xmm0, [a] pinsrw xmm0, [a+8], 4 来提高效率。对于大型数组,您可以使用paddb累积并在结尾处执行一个paddb

psadbw