© Иван Рощин, Москва
| ZXNet | : 500:95/462.53 |
| : bestview@mtu-net.ru | |
| WWW | : http://www.ivr.da.ru |
(«Радиомир. Ваш компьютер» 4/2003)
Наиболее часто используемый для сжатия ассемблерных программ упаковщик
HRUST Дмитрия Пьянкова, как я выяснил в результате экспериментов, сжимает даже
Итак, в чём сущность способа? Часто используется такой приём оптимизации, когда переменная размещается непосредственно в команде загрузки, тем самым сокращается объём программы и уменьшается время её выполнения (более подробно об этом вы можете прочитать в [1]).
Например, вместо такого:
VAR DS 1
.........
LD A,(VAR)
можно записать так (будет на 2 байта короче и на 6 тактов быстрее):
LD A,0
VAR EQU $-1
Если первоначальное значение переменной не используется в программе, то имеет смысл сделать это значение таким, чтобы после компиляции получилась последовательность одинаковых байтов, которая может быть сжата упаковщиком. То есть байт операнда должен равняться предыдущему байту. В данном случае предыдущий байт код операции «LD A,n», равный #3E, следовательно, пишем так:
LD A,#3E
VAR EQU $-1
В результате после компиляции соответствующий участок программы будет представлять собой два одинаковых байта #3E.
Такой способ можно применять и для других команд, в которых первоначальное
значение операнда не используется. Пусть, например, в программе есть команда
CALL 0, где вместо 0 перед выполнением этой команды записывается
Если в программе с помощью директив ассемблера DB, DW, DS определены переменные или участки памяти, первоначальное значение которых не используется, то для улучшения сжатия их тоже имеет смысл заполнять значением предыдущего байта. Пусть, например, в программе имеется такой фрагмент:
JP label1
DS 20
Команда JP в машинном коде представляется тремя байтами; последний из них старший байт операнда, то есть label1/256. Соответственно, в программе пишем:
JP label1
DS 20,label1/256
Итак, что мы имеем? При использовании описанного способа улучшения сжатия программисту приходится помнить коды операций (или каждый раз обращаться к справочнику, или набирать команду в отладчике и смотреть, какой получится код). К тому же, чтобы не запутаться, для каждой команды, в которой производится замена операнда, придётся писать комментарий, поясняющий, что указанное в команде значение нужно лишь для лучшего сжатия и не используется при работе программы.
Понятно, что это весьма неудобно. Поэтому я предлагаю реализовать поддержку этого способа улучшения сжатия на уровне компилятора. Можно использовать следующую форму записи: если в команде вместо значения операнда поставить символ «\», то компилятор заполнит байт (байты) операнда значением предыдущего байта памяти. Так, запись «LD A,\» будет эквивалентна «LD A,#3E», «CALL \» эквивалентна «CALL #CDCD», «JR \» «JR #18», и так далее. Команды «DB \», «DW \», «DS n,\» будут соответственно означать, что 1, 2, n байтов памяти заполняются значением предыдущего байта.
При этом программисту не надо будет помнить коды команд и комментировать такие участки программы; без всяких комментариев будет ясно, что такая команда используется для повышения степени сжатия программы.
Но пока таких компиляторов нет, и заменять операнды придётся вручную. Для удобства я приведу сведения о том, на что надо заменять операнд, для некоторых команд ассемблера.
LD A,\ = LD A,#3E LD IX,\ = LD IX,#2121 LD B,\ = LD B,#06 LD IY,\ = LD IY,#2121 LD C,\ = LD C,#0E LD SP,\ = LD SP,#3131 LD D,\ = LD D,#16 LD (HL),\ = LD (HL),#36 LD E,\ = LD E,#1E ADD A,\ = ADD A,#C6 LD H,\ = LD H,#26 ADC A,\ = ADC A,#CE LD L,\ = LD L,#2E SUB \ = SUB #D6 LD XH,\ = LD XH,#26 SBC A,\ = SBC A,#DE LD XL,\ = LD XL,#2E AND \ = AND #E6 LD YH,\ = LD YH,#26 OR \ = OR #F6 LD YL,\ = LD YL,#2E XOR \ = XOR #EE LD BC,\ = LD BC,#0101 JP \ = JP #C3C3 LD DE,\ = LD DE,#1111 JR \ = JR #18 LD HL,\ = LD HL,#2121 CALL \ = CALL #CDCD |
Кстати говоря, компилятор может действовать и более сложным образом, не обязательно заменяя байт (байты) операнда на значение предыдущего байта. Пусть в программе имеется следующая последовательность команд:
LD HL,#1234
CALL subr1
LD HL,\
CALL subr1
В этом случае выгоднее будет сделать операндом третьей команды не #2121, а
#1234 тогда после компиляции получим две идущие друг за другом
одинаковые группы из 6 байтов, которые сожмутся более эффективно. Так вот,
компилятор может отслеживать подобные ситуации и решать, какие значения будут
принимать байты операнда в
Ещё один вариант логики работы компилятора не принимать самому
решения о том, какими значениями заполнять байты операндов в
Но такой упаковщик надо ещё написать! Или, если это покажется более удобным, можно написать отдельную программу, которая брала бы описанные выше два файла и выдавала один файл с установленными значениями байтов, который и подаётся затем на вход упаковщику. Но, чтобы обеспечить наилучшее сжатие, эта программа должна знать, как именно осуществляется упаковка.
Кстати, в [2] я писал о способе повышения сжатия музыкальных модулей, основанном на автоматическом определении байтов модуля, первоначальные значения которых не используются при проигрывании (а значит, могут быть любыми). За счёт заполнения этих байтов значением предыдущего байта и достигалось повышение степени сжатия. Так вот, там можно точно так же выполнять замену не просто на значение предыдущего байта, а так, чтобы сжатие было наилучшим.
|
Другие мои статьи об усовершенствовании ассемблера:
|
|
Другие мои статьи о сжатии:
|
|
Другие мои статьи о различных приёмах оптимизации при программировании на ассемблере Z80:
|