Здесь рассмотрены некоторые способы, которые позволяют оптимизировать
программу. Большинство трюков – это использование документированных
возможностей, но возможно необычным образом.
Ясно, что все варианты не рассмотреть, а лишь несколько для
демонстрации, как нужно нестандартно подходить к вопросу оптимизации.
-
Косвенная адресация вместо прямой. Это очевидное решение,
т. к. (без)условный переход (или вызов подпрограммы) занимает две
команды, а то же самое с косвенной адресацией – только одну. Чем
больше таких вызовов, тем выгоднее.
-
Правильный порядок в стеке. Это так же
очевидное решение, при
котором порядок вычисления меняют так, чтобы операнды по максимуму
использовали стек (а не регистры памяти), и при этом располагались в
порядке последующего вычисления. Или ещё – использование стека
вычислений для дублирования числа. Пусть некое число нужно многократно
использовать в вычислениях. Чтобы не вызывать его несколько раз из
регистра памяти (а может даже и не сохранять), используется следующая
методика: число вычисляют раньше, чем положено, затем после него
делаются другие вычисления, но так, чтобы наше число
дошло
до
регистра T. Затем другие вычисления завершаются, а регистры стека
Y…T остаются заполнены нашим числом (причём и далее будут им
заполняться). Экономия в командах на вызов из регистра.
-
Использования побочной ветви адресного пространства для
сокращения программы. Эти способы уже были рассмотрены ранее в
разделе по адресному пространству.
-
Замена БП
01 на В/О.
Это работает, только если стек возврата адресов пустой (нулевой).
См. в приложении комментарий по команде
В/О.
На самом деле с механизмом возврата
на адрес 01 через В/О используются много разных
способов оптимизации. Вот пример: программа состоит из кода начальное подготовки
HEAD, основного цикла MAIN, который начинается с вызова попрограммы
SUB (она даже потом вызывается несколько раз, иначе зачем делать
подпрограммой). А в конце цикла делается возрат на начало
(Б/П 00 или
аналогичное). Так вот, оптимизация состоит в том, что программа
выстраивается в таком порядке:
по адресу 00
КБПR
(R – содержит некий адрес, с которого фактически начинается HEAD),
затем тело SUB без вызова через ПП (!).
По окончании процедуры пройдет возврат на адрес 01 по команде
В/О, с которого
продолжиться тело MAIN до адреса в регистре R, т.е. до начала цикла.
Где экономия? Почти всё то же, но нет одного вызова ПП,
а это одна (для косвенного) или две команды.
-
Совмещение адреса перехода и команды. В этом случае адрес
перехода для двойных команд (чаще всего БП)
используется одновременно и как обычная команда для другой
последовательности. Очевидно, что требуется знание кодов операций.
Благодаря наличию побочных ветвей адресации или её неоднозначности можно
подогнать
адрес под нужную команду. Например, последовательность
БП
53
x→Пd
можно заменить на
БП
4D,
т. к. адрес 4D = 53, а 4D = это
x→Пd.
Иногда ради такой подгонки
делают перестроение программы:
перемешивание независимых кусков программы, располагая их по
разным адресам. Сюда же относится пример из журнала
ТМ №9 за 1985:
# | |
00 |
01 |
02 |
03 |
04 |
05 |
06 |
07 |
08 |
09 |
60 | |
Fx<0 |
61 |
Fx≥0 |
63 |
С/П |
|
Когда перед остановкой выводилось содержимое нужного регистра при
проверке условия. Экономия на том, что адрес перехода совпадает с
командой извлечения из регистра.
-
Удаление условных операторов. Речь идёт о замене условных
операторов, которые обычно двойные, на простую арифметику.
Пусть есть часть программы, где при X ≠ 0 нужно к регистру R9
добавить единицу (некий счётчик). Решение в лоб:
# | |
00 |
01 |
02 |
03 |
04 |
05 |
06 |
07 |
08 |
09 |
00 | |
Fx≠0 |
06 |
П→x9 |
1 |
+ |
x→П9 |
|
Решение с удалением условия:
# | |
00 |
01 |
02 |
03 |
04 |
05 |
06 |
07 |
08 |
09 |
00 | |
КЗН |
П→x9 |
+ |
x→П9 |
|
В случае, если X может быть и отрицательным, будет чуть длиннее, но
всё равно короче прямого решения:
# | |
00 |
01 |
02 |
03 |
04 |
05 |
06 |
07 |
08 |
09 |
00 | |
КЗН |
К∣x∣ |
П→x9 |
+ |
x→П9 |
|
-
Нестандартное использование циклов FLx.
-
Знание того, что цикл по завершении оставит единицу, позволяет
не инициализировать его при повторном заходе.
-
Быстрая проверка на единицу содержимого регистров R0…R3 с
переходом при невыполнении. Иногда для этой возможности
переставляют регистры, т. е. специально используется R0…R3
вместо других для такой возможности.
-
Выполнение операций, не имеющих отношение к циклу. Например,
нужно в конце некой подпрограммы уменьшить счётчик попыток в
регистре R2 и перейти на адрес (пусть 77). Вместо
КП→x2
БП
77
которое, кстати, портит стек (и для исправления может
потребоваться ещё команда), сделать:
FL2
77.
Разумеется, счётчик должен не кончаться или сразу после этого кода
идёт проверка по его окончанию. При этом для
бесконечности
счётчика иногда делают его отрицательным, если в конце вычислений важна
только разница между началом и концом.
-
Проверка на больше/меньше единицы. Если известно, что число
не отрицательное, то вместо отнимания единицы и проверки на
больше/меньше нуля можно сразу взять
Flg и проверить на
больше/меньше нуля.
-
Остановка по ошибке при условии. Вот несколько способов
сгенерировать ошибку и сделать остановку без проверки условия:
-
Если ноль – F1/x
-
Если меньше или равно нулю –
Flg
-
Если меньше нуля –
F√
-
Если больше единицы –
Fcos-1 или
Fsin-1
-
Если больше или равно 100 –
F10x
-
Если дробная часть больше или равно 0.6 –
К°→′"
-
Вызов части подпрограммы. Пример, пусть есть некий алгоритм,
который особым образом обрабатывает число, но только целое или
дробное отдельно. А нужно сделать её более универсальной, т. е. для
любого числа. Можно сделать так:
# | |
00 |
01 |
02 |
03 |
04 |
05 |
06 |
07 |
08 |
09 |
00 | |
К{x} |
0 |
FВx |
К[x] |
ПП |
07 |
←→ |
|
… |
30 | |
+ |
В/О |
|
Что здесь происходит? Сначала подготавливается на будущее дробная
часть, затем заталкивается ноль и оставляется целая часть.
Далее вызывается часть кода (часть кода текущей подпрограммы),
которая делает обработку над целой частью, а в конце делает сложение
(с нулём, в данном случае). Затем целая и дробная часть меняются
местами и код повторяется, причём в конце сложение уже делает
объединение, а затем возврат. Где экономия? Если бы мы вызывали
обрабатываемую часть по очереди, то нам всё равно потребовалось бы
разделять на целую и дробную часть, обрабатывать по очереди, а затем
объединять сложением. Для этого потребовались бы всё те же команды,
кроме 0. Но при это пришлось бы делать дважды
ПП, а это две команды. Заменив на одну команду
0 и вызвав свой хвост
, мы сэкономили одну команду.
-
Совмещение констант и адресов перехода. В данном
случае речь
идёт о том, что некоторая константа, используемая для вычислений,
одновременно содержит и адрес перехода. Иногда это делают
искусственно, перемещая программу под значение константы, а иногда
удаётся совместить. Пример из практики автора – для битового
положения игрока использовался формат N.0000H, где N – некий
этаж
,
H – бит (число 1, 2, 4 или 8), а количество нулей в дробной части
определяет положение на этаже
. Движение по этажу
выполнялось над
дробной частью, путём умножения/деления на два (биты) и путем
умножения/деления десять (влево/вправо на этаже). Подводный камень –
в автоматическом округлении ПМК, возникающим при сложении чисел
разных порядков. В данном случае при дробной части =
0.000000H (H.|−07) и делении на 10 получается число вне этажа
,
H.|−08, которое должно обнулиться. Для H = 1, 2, 4 при сложении с
целым так и есть, но если H = 8, то происходит исключение: в
результате округления 8.|−08 превращается
в 1.|−07 (неожиданный
телепорт). Чтобы программа вела себя корректно, необходимо перед
сложением от полученного числа отнять некое значение в диапазоне
3.|−08 ⩽ X < 5.|−08, тогда это не испортит случая
H = 1, 2, 4, и сделает число с H = 8 обнуляемым при округлении.
Так вот, используя знание
косвенной адресации можно к
3.|−08 (или 4.|−08) добавить пару цифр.
Это на исправление округления
не скажется, но позволит использовать эти цифры как адрес перехода.
Пусть нужна косвенная адресация с адресом 77, тогда константа будет
3.77|−08. А если учесть, что при косвенной адресации оно будет
ненормализовано, то оно же использовалось для видеоизображения особой
ситуации: 0.0000377-03.