Трюки по оптимизации

Здесь рассмотрены некоторые способы, которые позволяют оптимизировать программу. Большинство трюков – это использование документированных возможностей, но возможно необычным образом. Ясно, что все варианты не рассмотреть, а лишь несколько для демонстрации, как нужно нестандартно подходить к вопросу оптимизации.

  1. Косвенная адресация вместо прямой. Это очевидное решение, т. к. (без)условный переход (или вызов подпрограммы) занимает две команды, а то же самое с косвенной адресацией – только одну. Чем больше таких вызовов, тем выгоднее.

  2. Правильный порядок в стеке. Это так же очевидное решение, при котором порядок вычисления меняют так, чтобы операнды по максимуму использовали стек (а не регистры памяти), и при этом располагались в порядке последующего вычисления. Или ещё – использование стека вычислений для дублирования числа. Пусть некое число нужно многократно использовать в вычислениях. Чтобы не вызывать его несколько раз из регистра памяти (а может даже и не сохранять), используется следующая методика: число вычисляют раньше, чем положено, затем после него делаются другие вычисления, но так, чтобы наше число дошло до регистра T. Затем другие вычисления завершаются, а регистры стека Y…T остаются заполнены нашим числом (причём и далее будут им заполняться). Экономия в командах на вызов из регистра.

  3. Использования побочной ветви адресного пространства для сокращения программы. Эти способы уже были рассмотрены ранее в разделе по адресному пространству.

  4. Замена БП01 на В/О. Это работает, только если стек возврата адресов пустой (нулевой). См. в приложении комментарий по команде В/О.

    На самом деле с механизмом возврата на адрес 01 через В/О используются много разных способов оптимизации. Вот пример: программа состоит из кода начальное подготовки HEAD, основного цикла MAIN, который начинается с вызова попрограммы SUB (она даже потом вызывается несколько раз, иначе зачем делать подпрограммой). А в конце цикла делается возрат на начало (Б/П00 или аналогичное). Так вот, оптимизация состоит в том, что программа выстраивается в таком порядке: по адресу 00 КБПR (R – содержит некий адрес, с которого фактически начинается HEAD), затем тело SUB без вызова через ПП (!). По окончании процедуры пройдет возврат на адрес 01 по команде В/О, с которого продолжиться тело MAIN до адреса в регистре R, т.е. до начала цикла. Где экономия? Почти всё то же, но нет одного вызова ПП, а это одна (для косвенного) или две команды.

  5. Совмещение адреса перехода и команды. В этом случае адрес перехода для двойных команд (чаще всего БП) используется одновременно и как обычная команда для другой последовательности. Очевидно, что требуется знание кодов операций. Благодаря наличию побочных ветвей адресации или её неоднозначности можно подогнать адрес под нужную команду. Например, последовательность БП53x→П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 С/П
    Когда перед остановкой выводилось содержимое нужного регистра при проверке условия. Экономия на том, что адрес перехода совпадает с командой извлечения из регистра.

  6. Удаление условных операторов. Речь идёт о замене условных операторов, которые обычно двойные, на простую арифметику.

    Пусть есть часть программы, где при 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

  7. Нестандартное использование циклов FLx.
    • Знание того, что цикл по завершении оставит единицу, позволяет не инициализировать его при повторном заходе.
    • Быстрая проверка на единицу содержимого регистров R0…R3 с переходом при невыполнении. Иногда для этой возможности переставляют регистры, т. е. специально используется R0…R3 вместо других для такой возможности.
    • Выполнение операций, не имеющих отношение к циклу. Например, нужно в конце некой подпрограммы уменьшить счётчик попыток в регистре R2 и перейти на адрес (пусть 77). Вместо КП→x2БП77 которое, кстати, портит стек (и для исправления может потребоваться ещё команда), сделать: FL277. Разумеется, счётчик должен не кончаться или сразу после этого кода идёт проверка по его окончанию. При этом для бесконечности счётчика иногда делают его отрицательным, если в конце вычислений важна только разница между началом и концом.

  8. Проверка на больше/меньше единицы. Если известно, что число не отрицательное, то вместо отнимания единицы и проверки на больше/меньше нуля можно сразу взять Flg и проверить на больше/меньше нуля.

  9. Остановка по ошибке при условии. Вот несколько способов сгенерировать ошибку и сделать остановку без проверки условия:
    • Если ноль – F1/x
    • Если меньше или равно нулю – Flg
    • Если меньше нуля – F
    • Если больше единицы – Fcos-1 или Fsin-1
    • Если больше или равно 100 – F10x
    • Если дробная часть больше или равно 0.6 – К°→′"

  10. Вызов части подпрограммы. Пример, пусть есть некий алгоритм, который особым образом обрабатывает число, но только целое или дробное отдельно. А нужно сделать её более универсальной, т. е. для любого числа. Можно сделать так:
     # |  00 01 02 03 04 05 06 07 08 09
     00 |  К{x} 0 FВx К[x] ПП 07 ←→
     30 |  + В/О
    Что здесь происходит? Сначала подготавливается на будущее дробная часть, затем заталкивается ноль и оставляется целая часть. Далее вызывается часть кода (часть кода текущей подпрограммы), которая делает обработку над целой частью, а в конце делает сложение (с нулём, в данном случае). Затем целая и дробная часть меняются местами и код повторяется, причём в конце сложение уже делает объединение, а затем возврат. Где экономия? Если бы мы вызывали обрабатываемую часть по очереди, то нам всё равно потребовалось бы разделять на целую и дробную часть, обрабатывать по очереди, а затем объединять сложением. Для этого потребовались бы всё те же команды, кроме 0. Но при это пришлось бы делать дважды ПП, а это две команды. Заменив на одну команду 0 и вызвав свой хвост, мы сэкономили одну команду.

  11. Совмещение констант и адресов перехода. В данном случае речь идёт о том, что некоторая константа, используемая для вычислений, одновременно содержит и адрес перехода. Иногда это делают искусственно, перемещая программу под значение константы, а иногда удаётся совместить. Пример из практики автора – для битового положения игрока использовался формат 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.