Методы оптимизации программного кода
для 8-битовых процессоров.
В настоящее время, в связи с появлением компьютеров нового поколения, программисты всё меньше внимания стали уделять компактности и быстродействию разрабатываемого программного обеспечения. Однако ещё совсем недавно, всего 10 лет назад дело обстояло совсем не так, как сейчас. Тогда в нашей стране компьютеры только начинали появляться. Это были не современные 16 и 32-битовые платформы, к которым мы уже успели так быстро привыкнуть, а это были в основном компьютеры, оснащённые 8-битовыми процессорами (с тактовой частотой порядка 1 Мгц), 64 Кб оперативной памяти, дисководами для дискет 360 Кб и так далее. Хорошим примером такого компьютера служит модель Пълдин-601/Пълдин-601A. Эти компьютеры в своё время были очень распространены в нашей стране и в странах бывшего СССP. Они были оснащены процессором СМ601 (аналог MC6800) и вся приводимая в статье информация будет приведена именно для них.
Компактность программ и их быстродействие выходили на первый план в те времена, когда были широко распространены Пълдины и подобные им компьютеры. Конечно, в настоящее время такие компьютеры если и остались, то для них уже никто не разрабатывает нового программного обеспечения. Поэтому, всё что приведено в этой статье может быть лишь хорошим путеводителем в историю, но может послужить и советом для современных начинающих программистов - ведь начинать всегда нужно с чего-то простого (т.е. с 8-битового ассемблера), а уже потом переходить к сложному (т.е. к 16 и 32-битовому ассемблеру).
Существует большое количество методов и приёмов оптимизации программного кода. Далее будут приведены лишь самые простые, но в то же время и самые наглядные примеры.
1) Использование инструкций bra и bsr вместо jmp и jsr принесёт выигрыш в 1 байт, так как bra и bsr - инструкции с относительной адресацией, т.е. двухбайтовые. Однако применять их можно только в том случае, если переход нужно совершить в пределах ближайших 255 байт (примерно по 127 байт в каждую сторону от данного места). Однако часто бывают случаи, что в какой-то области, размером около 255 байт, сконцентрировано очень много ОДИНАКОВЫХ переходов к одной и той же дальней метке (т.е. jmp). Тогда следует где-то приблизительно в середине этой области применить переход jmp, а во всех остальных местах - jmp заменить переходом bra на эту метку (т.е на этот jmp).
2) Инструкции типа ldaa #0 следует заменить на clra. А вместо конструкций типа
clra
staa per1
применять clr per1.
3) 1 байт можно выиграть, заменив конструкцию типа
ldx #per1
clr $00,x
clr $01,x
на
ldx #$0000
stx per1
4) Если требуется разделить или умножить 1-байтовое число на 2, 4, 8, 16 и так далее, то не обязательно прибегать к громоздким конструкциям с вызовом системных функций, предназначенных для этого. Следует воспользоваться общеизвестным фактом, что при сдвиге битов влево число умножается на 2, а при сдвиге вправо - делится на 2 и пользоваться инструкциями типа asra и asla.
5) Следует всегда продумывать, какой условный переход применить, чтобы избежать лишних безусловных переходов. Например, следующие две конструкции выполняют абсолютно одинаковую функцию, но вторая короче первой на 2 байта.
Первый вариант:
cmpa #12
bne per1
bra per2
per1 ...
rts
per2 ...
rts
Второй вариант:
cmpa #12
beq per2
...
rts
per2 ...
rts
6) Следующий хитрый приём даёт выигрыш 1 байт. Две следующие последовательности выполняют абсолютно одинаковую функцию.
Первый вариант:
set_A staa $06
Второй вариант:
set_A staa $06
DB $8c ; CPX #
set_B stab $05
...
В первом случае выполняется явный обход инструкции stab $05. А во втором - при переходе к метке set_A и выполнении инструкции staa $06, следующие 3 байта воспринимаются как CPX #$d705 и не выполняют ни полезных, ни вредных в данном случае, действий.
7) Из конструкции
ldaa per1
tsta
bne m2
можно абсолютно спокойно убрать инструкцию tsta. Всё будет работать абсолютно также, ведь ldaa и без того сама установит флаг нуля, если переменная per1 равна нулю.
8) Если необходимо сравнить какую-то переменную с числом 255, то вместо конструкции типа
ldaa per2
cmpa #$ff
beq m2
логично будет применить такой набор команд:
ldaa per2
inca
beq m2
Но здесь нужно быть внимательным, так как здесь, в отличие от приведённой выше конструкции с использованием cmpa, значение регистра "a" увеличивается на единицу. Но если далее это значение не используется, то этот метод вполне применим. Аналогично, можно применить deca вместо cmpa #$01.
9) Если требуется ненадолго запомнить какое-либо значение, то можно вместо того, чтобы выделять для этих целей переменную, воспользоваться стеком. Например, вместо
staa per1
...
ldaa per1
можно воспользоваться
psha
...
pula
Здесь выигрыш будет 5 байт (5ый байт за счёт того, что не нужно будет описывать переменную). Однако здесь следует быть очень внимательными и следить за тем, чтобы между psha и pula не изменялся стековый указатель (а это происходит при выполнении таких инструкций как txs, rts, bsr, jsr, lds). Недопустимо, чтобы psha был в основной программе, а pula в подпрограмме или наоборот. Также недопустимо, чтобы между "нашими" psha и pula число других psha/pshb было не равно числу других pula/pulb. В следующем примере как раз применяется этот приём.
10) Далее будет приведён классический пример процедуры, которая выводит на экран содержимое регистра "a" в шестнадцатеричном виде. На компьютере Пълдин-601 этот алгоритм применён в прерывании int $25, выполняющем соответствующие действия. И на этом примере будет показан ещё один метод оптимизации программного кода.
psha
lsra
lsra
lsra
lsra
anda #$0f
oraa #$30
cmpa #$3a
bcs prhexz1
adda #7
prhexz1 int $22
pula
anda #$0f
oraa #$30
cmpa #$3a
bcs prhexz2
adda #7
prhexz2 int $22
rts
Применим метод, очень похожий на рекурсию, но в данном случае подпрограмма вызывает не саму себя, а лишь свою часть и то в качестве "другой" подпрограммы. Инструкция rts, следующая последней, в первом случае служит для выхода из подпрограммы, вызываемой командой bsr, а во втором случае, из подпрограммы вообще. Посмотрим, насколько теперь сокращается размер этой подпрограммы.
psha
lsra
lsra
lsra
lsra
bsr prhex
pula
prhex anda #$0f
oraa #$30
cmpa #$3a
bcs prhexz1
adda #7
prhexz1 int $22
rts
Однако и это не предел. Оказывается, можно сократить размер этой подпрограммы ещё на 2 байта, изменив арифметику. В следующем примере используется инструкция десятичной коррекции сложения daa, которую программисты практически нигде не применяют на практике.
psha
lsra
lsra
lsra
lsra
bsr prhex
pula
anda #$0f
prhex adda #$90
daa
adca #$40
daa
int $22
rts
11) Наконец, следует разобраться, почему именно адреса $0000-$00ff сделаны рабочей областью BIOS, UniDOS и страничного ROM на компьютере Пълдин-601. Это сделано для того, чтобы уменьшить размер программного кода. Ведь основной блок BIOS должен уместиться в 4096 байт, а каждая страница памяти - в 8192 байт. Это достигается за счёт того, что инструкции типа ldaa, stx, cmpb и прочие при обращении к адресам $0000-$00ff кроме Extended-адресации (команда при этом имеет длину 3 байта) могут использовать Direct-адресацию (команда при этом имеет длину 2 байта). Очевидно, чтобы обратиться к адресам $0100-$ffff нужна обязательно трёхбайтовая инструкция. Вот поэтому адресное пространство $0000-$00ff и выделено для того, чтобы там размещали свои переменные BIOS, UniDOS и страничный ROM. В табл. 1 приводится сравнение для некоторых коротких подпрограмм, зашитых в BIOS компьютера Пълдин-601.
Табл. 1.
Функция | Длина кода | Длина кода, если бы применялась Extended адресация | Проигрыш |
int $2e | 38 | 46 | 8 |
int $2f | 56 | 68 | 12 |
int $37 | 36 | 40 | 4 |
int $2d | 94 | 124 | 30 |
Суммарно | 224 | 278 | 54 |
Уже в этих четырёх коротких подпрограммах, обслуживающих соответствующие прерывания, проигрыш составил бы 54 байта. А каков был бы проигрыш, если подобный расчёт сделать для всего BIOS! Тогда бы он абсолютно точно не уместился в 4096 байт, а страничный ROM - в 8192 байта. Например, в странице UniBIOS нет ни одного "свободного байта" - по адресу $d7fe - rts последней подпрограммы, в $d7ff - байт, переводящий контрольную сумму в ноль, а с адреса $d800 (и до конца, т.е. до $dfff) расположены шрифты 8x8 (2048 байт).
< < < На главную страничку < < <