В моей программе сборки я пытаюсь вычислить уравнение (((((2 ^ 0 + 2 ^ 1) * 2 ^ 2) + 2 ^ 3) * 2 ^ 4) + 2 ^ 5) - PullRequest
0 голосов
/ 10 ноября 2018

В моей программе сборки 80x86 я пытаюсь вычислить уравнение (((((2 ^ 0 + 2 ^ 1) * 2 ^ 2) + 2 ^ 3) * 2 ^ 4) + 2 ^ 5) ... (2 ^ n), где каждому четному показателю предшествует умножение и каждому нечетному показателю предшествует плюс. У меня есть код, но мой результат постоянно отличается от желаемого результата. Когда 5 для n, результат должен быть 354, но я получаю 330.

Любой и все советы будут оценены.

.586
.model flat

include io.h

.stack 4096

.data
number dword ?
prompt byte "enter the power", 0
string byte 40 dup (?), 0
result byte 11 dup (?), 0
lbl_msg byte "answer", 0
bool dword ?
runtot dword ?

.code
_MainProc proc
    input prompt, string, 40
    atod string
    push eax


    call power



    add esp, 4

    dtoa result, eax
    output lbl_msg, result

    mov eax, 0
    ret

_MainProc endp

power proc
    push ebp
    mov ebp, esp

    push ecx

    mov bool, 1     ;initial boolean value
    mov eax, 1
    mov runtot, 2   ;to keep a running total
    mov ecx, [ebp + 8]

    jecxz done

loop1:
    add eax, eax        ;power of 2
    test bool, ecx      ;test case for whether exp is odd/even
    jnz oddexp          ;if boolean is 1
    add runtot, eax     ;if boolean is 0
    loop loop1

oddexp:
    mov ebx, eax        ;move eax to seperate register for multiplication
    mov eax, runtot     ;move existing total for multiplication
    mul ebx             ;multiplication of old eax to new eax/running total
    loop loop1

done:
    mov eax, runtot     ;move final runtotal for print
    pop ecx
    pop ebp
    ret




power endp



end

1 Ответ

0 голосов
/ 10 ноября 2018

Вы слишком усложняете свой код статическими переменными и ветвлениями.

Это степени 2, вы можете (и должны) просто сдвинуть влево на n вместо фактического построения 2^n и использования инструкции mul.

add eax,eax - это лучший способ умножения на 2 (то есть сдвиг влево на 1), но не ясно, почему вы делаете это со значением в EAX на тот момент. Это либо результат умножения (который, вероятно, следовало бы сохранить в runtot после mul), либо сдвиг влево на 1 после четной итерации.

Если вы пытались создать переменную 2^i (с оптимизацией снижения прочности для смещения на 1 каждую итерацию вместо смещения на i), то ваша ошибка заключается в том, что вы заклинили EAX с помощью mul, и его в блоке oddexp.

Как указывает Шут, если первый loop loop1 провалится, он провалится в oddexp:. Когда вы выполняете дублирование хвоста цикла, убедитесь, что вы продумали, где провал будет с каждого хвоста, если цикл там заканчивается.


Также нет смысла иметь статическую переменную с именем bool, которая содержит 1, которую вы используете только в качестве операнда для test. Это подразумевает для читателей, что маску иногда нужно менять; test ecx,1 намного понятнее, как способ проверки младшего бита на ноль / ненулевое значение.

Вам также не нужно статическое хранилище для runtot, просто используйте регистр (например, EAX, где вы все равно хотите получить результат). 32-битный x86 имеет 7 регистров (не считая указателя стека).


Вот как я это сделаю. Не проверено, но я упростила многое, развернув на 2. Затем тест на нечетность / четность уходит, потому что этот чередующийся шаблон жестко запрограммирован в структуру цикла.

Мы дважды увеличиваем и сравниваем / разветвляемся в цикле, поэтому развертывание не избавило от накладных расходов цикла, а просто изменило одну из ветвей цикла на if() break, который может оставить цикл из середины.

Это не самый эффективный способ написать это; проверку приращения и раннего выхода в середине цикла можно было бы оптимизировать, считая еще один счетчик с n и выходя из цикла, если осталось менее 2 шагов. (Потом разберись в эпилоге)

;; UNTESTED
power proc   ; fastcall calling convention: arg: ECX = unsigned int n
             ; clobbers: ECX, EDX
             ; returns: EAX

    push  ebx           ; save a call-preserved register for scratch space

    mov   eax, 1        ; EAX = 2^0   running total / return value
    test  ecx,ecx
    jz    done

    mov   edx, ecx      ; EDX = n
    mov   ecx, 1        ; ECX = i=1..n loop counter and shift count

loop1:                  ; do{   // unrolled by 2
    ; add 2^odd power
    mov   ebx, 1
    shl   ebx, cl         ; 2^i         ; xor   ebx, ebx; bts   ebx, ecx
    add   eax, ebx        ; total += 2^i

    inc   ecx
    cmp   ecx, edx
    jae   done            ; if (++i >= n) break;

    ; multiply by 2^even power
    shl   eax, cl       ; total <<= i;  // same as total *= (1<<i)

    inc   ecx           ; ++i
    cmp   ecx, edx
    jb    loop1         ; }while(i<n);

done:
    pop  ebx
    ret

Я не проверял, выдает ли шаг добавления нечетной мощности перенос в другой бит. Я думаю, что это не так, поэтому было бы безопасно реализовать его как bts eax, ecx (установка бита i). Эффективно ИЛИ вместо ДОБАВЛЕНИЯ, но они эквивалентны, если бит был предварительно очищен.

Чтобы ассемблер выглядел больше как источник и избегал непонятных инструкций, я реализовал 1<<i с shl для генерации 2^i для total += 2^i вместо более эффективного-на-Intel xor ebx,ebx / bts ebx, ecx. (Сдвиги с переменным числом в семействе Intel Sandybridge составляют 3 мопа из-за устаревшего багажа с обработкой флагов x86: флаги должны быть не тронуты, если count = 0). Но это хуже на AMD Ryzen, где bts reg,reg равно 2 моп, а shl reg,cl равно 1.


Обновление: i=3 создает перенос при переносе, поэтому мы не можем использовать бит OR или BTS для этого случая. Но оптимизация возможна с большим количеством ветвлений.

Использование calc:

; define shiftadd_power(n) { local res=1; local i; for(i=1;i<=n;i++){ res+=1<<i; i++; if(i>n)break; res<<=i;} return res;}
shiftadd_power(n) defined
; base2(2)

; shiftadd_power(0)
        1 /* 1 */
...

Первые несколько выходов:

n          shiftadd(n) (base2)

0                   1
1                  11
2                1100
3               10100     ; 1100 + 1000 carries
4           101000000
5           101100000     ; 101000000 + 100000 set a bit that was previously 0
6     101100000000000
7     101100010000000     ; increasing amounts of trailing zero around the bit being flipped by ADD

Очистка первых 3 итераций включит оптимизацию BTS, , где вы просто устанавливаете бит вместо фактического создания 2^n и добавления.

Вместо просто очистки, мы можем просто жестко закодировать начальную точку для i=3 для большего n и оптимизировать код, который вычисляет возвращаемое значение для случая n<3. Я придумал формулу без ответвлений, основанную на смещении вправо 0b1100 битового шаблона на 3, 2 или 0.

Также обратите внимание, что при n> = 18 счетчик последнего сдвига строго больше половины ширины регистра, а 2 ^ i из нечетного i не имеет младших битов . Таким образом, только последние 1 или 2 итерации могут повлиять на результат. Он сводится к 1<<n для нечетных n или 0 для четных n. Это упрощает до (n&1) << n.

Для n=14..17 установлено максимум 2 бита. Начиная с result = 0 и делая последние 3 или 4 итерации, должно быть достаточно, чтобы получить правильную сумму. Фактически, для любых n нам нужно только выполнить последние k итераций, где k достаточно, чтобы общее число сдвигов от четных i было> = 32. Любые биты, установленные более ранними итерациями, сдвигаются из. (Я не добавил ветку для этого особого случая.)

;; UNTESTED
;; special cases for n<3, and for n>=18
;; enabling an optimization in the main loop (BTS instead of add)
;; funky overflow behaviour for n>31: large odd n gives 1<<(n%32) instead of 0
power_optimized proc
     ; fastcall calling convention: arg: ECX = unsigned int n <= 31
     ; clobbers: ECX, EDX
     ; returns: EAX

    mov   eax, 14h      ; 0b10100 = power(3)
    cmp   ecx, 3
    ja    n_gt_3        ; goto main loop or fall through to hard-coded low n
    je    early_ret
    ;; n=0, 1, or 2  =>  1, 3, 12  (0b1, 0b11, 0b1100)

    mov   eax, 0ch      ; 0b1100  to be right-shifted by 3, 2, or 0
    cmp   ecx, 1        ; count=0,1,2 => CF,ZF,neither flag set
    setbe cl            ; count=0,1,2 => cl=1,1,0
    adc   cl, cl        ;                   3,2,0  (cl = cl+cl + (count<1) )
    shr   eax, cl
early_ret:
    ret

large_n:                ; odd n: result = 1<<n.  even n: result = 0
    mov   eax, ecx
    and   eax, 1        ; n&1
    shl   eax, cl       ; n>31 will wrap the shift count so this "fails"
    ret                 ; if you need to return 0 for all n>31, add another check

n_gt_3:
    ;; eax = running total for i=3 already
    cmp   ecx, 18
    jae   large_n

    mov   edx, ecx      ; EDX = n
    mov   ecx, 4        ; ECX = i=4..n loop counter and shift count

loop1:                  ; do{   // unrolled by 2
    ; multiply by 2^even power
    shl   eax, cl       ; total <<= i;  // same as total *= (1<<i)

    inc   edx
    cmp   ecx, edx
    jae   done            ; if (++i >= n) break;

    ; add 2^odd power.  i>3 so it won't already be set (thus no carry)
    bts   eax, edx      ; total |= 1<<i;

    inc   ecx           ; ++i
    cmp   ecx, edx
    jb    loop1         ; }while(i<n);

done:
    ret

Использование BTS для установки бита в EAX позволяет избежать необходимости в дополнительном чистом регистре для создания 1<<i, поэтому нам не нужно сохранять / восстанавливать EBX. Так что это небольшая бонусная экономия.

Обратите внимание, что на этот раз основной цикл вводится с i=4, который является четным, вместо i=1. Поэтому я поменялся надстройкой против сдвига.

Я все еще не удосужился вытянуть cmp / jae из середины петли. Нечто подобное lea edx, [ecx-2] вместо mov установило бы условие выхода из цикла, но потребовало бы проверки, чтобы вообще не запускать цикл для i = 4 или 5. Для пропускной способности большого количества многие ЦП могут выдержать 1 взятый + 1 не взятая ветвь каждые 2 такта, не создавая худшего узкого места, чем переносимые по петле цепи dep (через eax и ecx). Но прогноз ветвления будет другим, и он использует больше записей буфера порядка ветвления, чтобы записать больше возможных точек отката / быстрого восстановления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...