Некоторое время назад задавал вопрос на канале sizecoding, ответа не получил. Три дня назад нашел решение:
Расс Кокс рассказывает о супероптимизации - технике поиска самого быстрого куска кода путем полного перебора инструкций процессора (конечно, это работает только для очень коротких кусков кода). Любопытный пример там - самое быстрое воплощение функции знака (-1, 0 или 1 в зависимости от знака аргумента) на ассемблере 80x86. До такого вручную действительно додуматься нелегко:
cwd
neg ax
adc dx, dx
Здесь начальный аргумент лежит в ax, значение помещается в dx. Работает следующим образом: сначала cwd расширяет знак ax в dx, после чего в dx лежит -1, если аргумент отрицательный, а иначе 0. neg ax меняет знак ax, но это на самом деле неважно, а важно то, что эта инструкция помещает в флаг carry единицу, только если аргумент был ненулевой.
Наконец adc dx, dx складывает dx с самим собой, добавляет carry от второй инструкции, и помещает результат в dx. В результате выходит:
- если ax<0, то dx вначале -1, carry равен 1, результат равен -1*2+1 = -1 (во как!)
- если аx=0, то dx вначале 0, carry равен 0, результат 0*2+0=0
- если ax>0, то dx вначале 0, carry равен 1, результат 0*2+1=1
cwd
neg ax
adc dx, dx
Здесь начальный аргумент лежит в ax, значение помещается в dx. Работает следующим образом: сначала cwd расширяет знак ax в dx, после чего в dx лежит -1, если аргумент отрицательный, а иначе 0. neg ax меняет знак ax, но это на самом деле неважно, а важно то, что эта инструкция помещает в флаг carry единицу, только если аргумент был ненулевой.
Наконец adc dx, dx складывает dx с самим собой, добавляет carry от второй инструкции, и помещает результат в dx. В результате выходит:
- если ax<0, то dx вначале -1, carry равен 1, результат равен -1*2+1 = -1 (во как!)
- если аx=0, то dx вначале 0, carry равен 0, результат 0*2+0=0
- если ax>0, то dx вначале 0, carry равен 1, результат 0*2+1=1
Или еще одно:
long sgn(long x)
{
_asm {
mov eax, x
cdq
cmp edx, eax
xchg eax, edx
adc al, 0
}
}
cdq используется для заполнения edx в зависимости от знака eax. Т.е. уже после cdq ответ в виде значения edx нас бы почти устроил. Осталось лишь выделить два случая, когда eax=0 и eax>0. Для этого выставляют знак переноса при помощи cmp, а потом его (флаг CF) добавляют к al.
{
_asm {
mov eax, x
cdq
cmp edx, eax
xchg eax, edx
adc al, 0
}
}
cdq используется для заполнения edx в зависимости от знака eax. Т.е. уже после cdq ответ в виде значения edx нас бы почти устроил. Осталось лишь выделить два случая, когда eax=0 и eax>0. Для этого выставляют знак переноса при помощи cmp, а потом его (флаг CF) добавляют к al.
для Z80 решение вышло так:
device zxspectrum48
ORG #6000
begin
ld hl,$8000
flp: ld a,l
or a
jr z,put
or a
add a,a
sbc a,a
or 1
put:ld (hl),a
inc l
jr nz,flp
jr $
end
display /d,end-begin
savesna "!void.sna",begin
Если постараться отойти от условного смещения, то вышло немного длинно:
device zxspectrum48
ORG #6000
begin
jr $
ld a,$55
or a
push af
pop bc
;SZ5H3VNC
;76543210
ld a,c;c=$44
and 64
xor 64
rlca
rlca
and 1
ld c,a
ld a,b
add a,a
sbc a,a
or c
jr $
end
display /d,end-begin
savesna "!void.sna",begin
Комментарии
Отправить комментарий