所以我有这个问题我应该解决,我花了好几个小时试图找出最好的方法来做到这一点,谷歌没有太多的帮助。
问题是创建一个子程序,该子程序被赋予一个单词列表,然后您将该单词列表添加到另一个成为输出的列表中。它基本上是一种处理大量数据的方法。
我的代码适用于字样中的字符,但对于从一个完整字到另一个字的进位标记,它不起作用。第一个16位字(下例中的0005)是一个标志,用于告诉我的子程序有多少个字。
例如,给出以下输入,
//si 0005 0000 EEEE DDDD CCCC BBBB
//di 0005 0000 1111 2222 3333 4445
预期输出为:
0005 0001 0000 0000 0000 0000
我的代码产生:
0005 0000 FFFF FFFF FFFF 0000
我相信我理解为什么这种情况在大多数情况下会发生,但我不确定解决这个问题的最佳方法。我需要一种低成本的方法,在不同的数据块之间传输1。
;---------------------------------------
; ADD Subroutine
;---------------------------------------
.data
bxx dw 0000h ;
cxx dw 0000h ;
.code
;---------------------------------------
addx: ;
mov bxx, bx ;save incoming register
mov cxx, cx ;save incoming register
mov bx, si ;move n to bl - acts as a cursor
loopAdd: ;loop point
mov cx, [si+bx] ;move word at point si+bx into register cx
ADC [di+bx], cx ;add with carry
sub bx, 0002h; ;decrement cursor by a full word
cmp bx, 0000h ;bx == 0?
jg loopAdd ;no? jump to loop point
end: ;
mov bx, bxx ;return register to original state
mov cx, cxx ;return register to original state
ret ;return
;---------------------------------------
答案 0 :(得分:2)
您必须保存上一次迭代中的进位标志。
试试这个:
;---------------------------------------
; ADD Subroutine
;---------------------------------------
.data
bxx dw 0000h ;
cxx dw 0000h ;
.code
;---------------------------------------
addx: ;
mov bxx, bx ;save incoming register
mov cxx, cx ;save incoming register
mov bx, si ;move n to bl - acts as a cursor
clc ;clear carry flag
pushf ;save flag register
loopAdd: ;loop point
mov cx, [si+bx] ;move word at point si+bx into register cx
popf ;restore saved flag register
ADC [di+bx], cx ;add with carry
pushf ;save flag register
sub bx, 0002h; ;decrement cursor by a full word
jg loopAdd ;if the result is positive, jump to loop point
end: ;
popf ;remove saved flag register from the stack
mov bx, bxx ;return register to original state
mov cx, cxx ;return register to original state
ret ;return
;---------------------------------------
请注意,cmp bx, 0000h
不需要cmp
,因为sub
为cmp
,但sub
只修改了标记并且不存储计算值,所以你可以直接检查printRectangles
的结果。
答案 1 :(得分:2)
在进行多精度算术时,你可以得到一个非常好的改进,假设你的multiprecison值是“大”(包含许多字值),并且将内循环展开N次,避免循环计数/进位标志展开部分内部的损坏。 (如果您的多精度算法不是很多,则不需要进行大量优化。)
我在这里修改了@ MikeCAT的答案,假设展开应该是8次迭代。
代码有3个部分:确定如何处理8个单词的片段, 以展开的方式处理片段,然后在主展开的循环中有效地处理多个8个字块。
对于OP的5个单词的示例,此例程永远不会进入完全展开的循环。对于大的多字符值,它确实如此,我希望这个例程可能非常快。
[以下代码未经测试。]
;---------------------------------------
; ADD Subroutine
; si = pointer to count word of 1st multiprecision value
; di = pointer to count word of 2nd multiprecision value
; assert: si->count == di ->count
; preserves si, di; exits with carry from addition
;---------------------------------------
sizeofword equ 2
;---------------------------------------
add_multiple: ; destroys ax, si, di
push cx ;save incoming register
mov cx, [si]
lea si, sizeofword[si+cx] ; find least significant word
lea di, sizeofword[di+cx]
; determine entry point into unrolled loop by taking counter modulo 8
mov cx, si ;move n to bl - acts as a cursor
shr cl, 1
jc add_xx1
je done ; edge case: 0 words in value
add_xx0:
shr cl, 1
jc add_x10
add_x00:
shr cl, 1
jnc add_000 ; note carry flag is clear
; clc
; jmp add_100
mov ax, 0[si]
add 0[di], ax ; do 1st add without carry
lea si, -1[si]
lea di, -1[di]
jmp add_011
add_x10:
shr cl, 1
jnc add_010
; clc
; jmp add_110
mov ax, 0[si]
add 0[di], ax
lea si, -1[si]
lea di, -1[di]
jmp add_101
add_x01:
shr cl, 1
jnc add_001
; clc
; jmp add_101
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
jmp add_100
add_xx1:
shr cl, 1
jnc add_x01
add_x11:
shr cl, 1
jnc add_011
; clc
; jmp add_111
; the following code adds a fragment of an 8 word block
add_111: ; carry bit has value to propagate
mov ax, 0[si]
; adc 0[di], ax
add 0[di], ax ; no carry in on 1st add
lea si, -1[si]
lea di, -1[di]
add_110:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_101:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_100:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_011:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_010:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_001:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_000:
mov ax, 0[si]
adc 0[di], ax
dec cx ; does not disturb carry
lea si, -1[si]
lea di, -1[di]
je done
; unrolled loop here; very low overhead
add_8words: ; carry bit has value to propagate
mov ax, 0[si]
adc 0[di], ax
mov ax, -1[si]
adc -1[di], ax
mov ax, -2[si]
adc -2[di], ax
mov ax, -3[si]
adc -3[di], ax
mov ax, -4[si]
adc -4[di], ax
mov ax, -5[si]
adc -5[di], ax
mov ax, -6[si]
adc -6[di], ax
mov ax, -7[si]
adc -7[di], ax
dec cx
lea si, -8[si]
lea di, -8[di]
jne add_8word
done: pop cx
ret
;---------------------------------------
序列
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
建议可能使用单字块移动指令作为替代:
std ; want to step backward
...
lods
adc ax, 0[di]
stos
...
cld
ret
对代码进行适当调整,留给读者。
我写的循环或LODS / STOS版本是否更快是值得仔细衡量的。
答案 2 :(得分:2)
如果您想要快速多精度添加,请尽可能使用64位代码。使用每条指令直接执行4倍宽度可实现4倍的加速。在386兼容的CPU上,即使在16位模式下也可以使用32位指令和寄存器,这将为您提供2倍的加速。
将艾拉的展开建议更进一步
lea
adc
,这在英特尔上很慢。
... set up for the unrolled loop, with Ira's setup code
; di = dst pointer
; bx = src-dst, so bx+di = src
add_8words: ; carry bit has value to propagate
;sahf ; another way to avoid a partial-flag stall while looping
mov ax, 0[bx+di]
adc ax, 0[di]
mov 0[di], ax
mov ax, -1[bx+di]
adc ax, -1[di]
mov -1[di], ax
mov ax, -2[bx+di]
adc ax, -2[di]
mov -2[di], ax
mov ax, -3[bx+di]
adc ax, -3[di]
mov -3[di], ax
mov ax, -4[bx+di]
adc ax, -4[di]
mov -4[di], ax
mov ax, -5[bx+di]
adc ax, -5[di]
mov -5[di], ax
mov ax, -6[bx+di]
adc ax, -6[di]
mov -6[di], ax
mov ax, -7[bx+di]
adc ax, -7[di]
mov -7[di], ax
lea di, -8[di]
; lahf
; put the flag-setting insn next to the branch for potential macro-fusion
dec cx ; causes a partial-flag stall, but only once per 8 adc
jne add_8word
这应该在Intel Broadwell上的每个时钟(减去环路开销)接近adc
,在AMD K8上通过Bulldozer运行。 (我忘记了如果k8 / k10每个时钟可以做两次加载。如果它能够做到这一点就会成为瓶颈。)根据{{3}},使用具有内存目的地的adc
对于英特尔来说并不好,但在AMD上则不错。英特尔Haswell及更早版本将受adc
的2c延迟限制。 (Broadwell发出adc
和cmov
单uop指令,利用Haswell中添加的3依赖性uop支持,因此FMA可以是单个uop指令。
loop
insn可能是旧CPU的胜利,其中部分reg失真非常糟糕,但其他避免部分标志失速的方法甚至可能比Agner Fog's tables更好。
使用dest-source技巧减少了递减指针的循环开销。负载中的2寄存器寻址模式不需要微熔丝,因为纯mov
负载无论如何都是单个uop。商店确实需要微熔丝,所以你应该更喜欢商店的单寄存器寻址模式。 Has7在port7上的额外存储地址单元只能处理简单的寻址模式,the slow loop
instruction。
另请参阅2-register addressing modes can't micro-fuse以获取有关multiprecision adc循环的信息,以及有关部分标记停顿性能的Core2与SnB CPU的一些实验。
此处循环的另一种方式是lea si, -1[si]
/ mov cx, si
/ jcxz
。 16位很糟糕,您无法在有效地址中使用[cx]
。