2016-03-03 6 views
1

Итак, у меня есть эта проблема, которую я должен решить, и я потратил часы, пытаясь найти лучший способ сделать это, Google не очень помог.Masm assembly 8086 нести флаг между добавлением слова данных

Проблема заключается в создании подпрограммы, которой присваивается список слов, которые вы затем добавляете с другим списком, который становится выходом. Это в основном метод работы с большими числами.

Мой код отлично подходит для флагов переноса в пределах слов, но для флага переноса от одного полного слова к другому он не работает. Первое 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

Remainder: 'cmp' будет изменять флаг' CF', в то время как 'mov' не будет. – MikeCAT

ответ

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 непосредственно за исключением.

+2

'pushf' /' popf' - действительно неуклюжее решение.Использование 'lea bx, [bx-2]'/'mov cx, bx' /' jcxz' цикла, не влияя на флаги, будет быстрее или использовать инструкцию 'loop' (хотя и медленная). 16-разрядные эффективные адреса не позволяют масштабировать индекс, IIRC, иначе вы могли бы также зацикливаться на 'dec' (который не влияет на флаг переноса), но это вызывает замедление или замедление частичного флага на большинстве процессоров. (Гораздо более суровый на pre-Sandybridge.) 'Lahf/sahf' - еще один вариант: эти инструкции быстро работают на Intel (но немного медленнее на AMD). –

+0

Спасибо за помощь! – Harrison

2

ОП говорит, что он хочет недорогое решение для сохранения переноса между итерациями. @MikeCAT: a решение; @PeterCordes предложил некоторые улучшения.

Есть еще одно действительно хорошее улучшение, которое вы можете получить при выполнении арифметики multiprecision, исходя из предположения, что ваше многозначное значение «большое» (содержит много значений слова), и это разворачивает внутренний цикл N раз, избегая подсчета/переноса петли повреждение флага внутри развернутой секции. (Если ваша многоточечная арифметика не очень много, вам не нужна большая оптимизация).

Я пересмотрел здесь ответ MikeCAT, исходя из предположения, что разворачивание должно состоять из 8 итераций.

Код содержит 3 части: определение того, как обрабатывать фрагмент из 8 слов, , обрабатывая фрагмент разворотным способом, а затем обрабатывая несколько 8 слотов слов эффективно в основном развернутом цикле.

Для примера OPs из 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 быстрее, это то, что нужно тщательно измерить.

+0

Да, разворачивание - огромная победа с 'adc', потому что цикл более дорогой, чем обычно. Я пытался сохранить его просто. См. Также http://stackoverflow.com/a/32087095/224132, включая комментарии. Я уверен, что версия 'lods' /' stos' будет хуже. lods и stos - это 3-х или 3-х точные инструкции для процессоров Intel и AMD. ('lodsd' является инструкцией 2 uop на Haswell и позже, но lodsw все еще 3). Один магазин за такт должен быть узким местом пропускной способности, но пропускная способность uop будет узким местом с лодками/stos –

+1

Однако, 'adc m, r/i' составляет 4 выхода с пропускной способностью по одному на 2 c, даже на Broadwell/Skylake, где' adc r , r/i' - один uop с задержкой 1 c. (Haswell и ранее имеют задержку в 2 c 'adc', поскольку uop не может иметь более двух входных зависимостей. Haswell имел FMA, Broadwell расширил эту функцию с тремя входами до' adc'. И 'adc r, m' can ' t micro-fuse на BDW, так что это всегда 2 uops. Однако на Haswell и ранее 'adc r, m' и' adc r, r' равны 2 uops. Таким образом load/adc r, m/store (с ' mov'), вероятно, оптимально: 4 uops per 'adc', насыщая интерфейс, где adc имеет задержку 1 c. –

+0

В Intel pre-Broadwell вы фактически ограничены латентностью adc, до половины этой пропускной способности. слон в комнате: 64-битный код будет в 4 раза быстрее, потому что каждый 64-битный 'adc' мог выполнять четыре команды 16 бит' adc'. –

2

Если вы хотите быстрое многоточное добавление, используйте 64-битный код, если это вообще возможно. Выполнение 4x ширины с каждой инструкцией дает 4x ускорение. На 386-совместимых процессорах вы можете использовать 32-битные инструкции и регистры даже в 16-битном режиме, что даст вам ускорение 2x.


Для того, чтобы раскатать предложение Ире шаг вперед

  • уменьшая накладные расходы цикла на один lea
  • избежать adc с адресатом памяти, которая медленно на Intel.

... 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 

Это должно работать на почти один adc за такт (накладные петли минус) на Intel Бродуэлла, и на AMD K8 через Bulldozer. (Я забываю, если k8/k10 может выполнять две нагрузки за такт. Это будет узким местом, если это невозможно). Использование adc с назначением памяти не подходит для Intel, но отлично подходит для AMD, согласно Agner Fog's tables. Intel Haswell и ранее будут ограничены 2c латентностью adc. (Бродвелл произвел adc и cmov одноуровневые инструкции, используя 3-зависимую поддержку uop, добавленную в Haswell, поэтому FMA может быть одной командой uop).

Использование трюка dest-source уменьшает накладные расходы цикла, уменьшая указатель. Режимы адресации 2-регистров в нагрузках не нуждаются в микроплавких предохранителях, потому что чистые mov нагрузки в любом случае равны. магазины нуждаются в микро-предохранителе, поэтому вам следует выбирать режимы адресации с одним регистром для магазинов. Дополнительный адресный блок Haswell на порту7 может обрабатывать только простые режимы адресации и 2-register addressing modes can't micro-fuse.

См. Также Problems with ADC/SBB and INC/DEC in tight loops on some CPUs для получения информации о многоточечных контурах adc и некоторых экспериментах на процессорах Core2 и SnB для производительности стойки с частичным флагом.

Другим способом петли здесь будет lea si, -1[si]/mov cx, si/jcxz. 16bit отстой, и вы не можете использовать [cx] в эффективном адресе.

+2

О, хороший трюк с индексными регистрами. Старая рука узнает новый трюк. –

+0

Спасибо, что нашли время, чтобы написать все это. Я закончил использовать инструкции цикла и adc - определенно узнал что-то здесь. Я знаю, что цикл должен быть медленным, но это работает в dosbox. – Harrison

+0

@ Харрисон: DOSBOX - чистый эмулятор, верно? Он вообще не запускает ваш код? Он эмулирует 386-совместимый CPU (или даже P6 с MMX/SSE?), Поэтому вы могли бы использовать 32-битный 'adc' для выполнения одного и того же цикла, но с удвоенным объемом данных на итерацию. Все проблемы с обработкой флагов петель 'adc' идентичны между 16 и 32 битами, хотя вы можете использовать' lea' для добавления без флага в любой регистр, который вам нравится. –