ZXNet эхоконференция «code.zx»


тема: Улучшение сжатия программ на ассемблерe Z80



от: Ivan Roshin
кому: All
дата: 19 Apr 2003
Hello, All!

═══════════════════ bestpack.t ══════════════════

(c) Иван Рощин, Москва

Fido : 2:5020/689.53
ZXNet : 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW : http://www.ivr.da.ru

Улучшение сжатия программ на ассемблере Z80
═══════════════════════════════════════════

"Радиомир. Ваш компьютер" 4/2003.
Дата последнего редактирования: 27.12.2002.

Hаиболее часто используемый для сжатия ассемблерных программ
упаковщик HRUST Дмитрия Пьянкова, как я выяснил в результате
экспериментов, сжимает даже два-три стоящих рядом одинаковых
байта. Здесь я хочу рассказать о том, как это можно использовать
для улучшения сжатия программ. Улучшение сжатия сэкономит
занимаемое программой место на диске, а если программа написана
так, что её отдельные части хранятся в памяти в упакованном виде
и распаковываются при необходимости, то это поможет и сэкономить
место в памяти.
Итак, в чём сущность способа? Часто используется такой приём
оптимизации, когда переменная размещается непосредственно в
команде загрузки, тем самым сокращается объём программы и
уменьшается время её выполнения (более подробно об этом вы
можете прочитать в [1]).
Hапример, вместо такого:

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 перед
выполнением этой команды записывается какой-то адрес. Точно так
же, для повышения степени сжатия делаем операнд равным не 0, а
числу, состоящему из байтов, равных предыдущему байту (коду
операции CALL nn), в результате получаем CALL #CDCD.
Если в программе с помощью директив ассемблера 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 байтов памяти заполняются
значением предыдущего байта.
При этом программисту не надо будет помнить коды команд
и комментировать такие участки программы; без всяких
комментариев будет ясно, что такая команда используется для
повышения степени сжатия программы.
Hо пока таких компиляторов нет, и заменять операнды придётся
вручную. Для удобства я приведу сведения о том, на что надо
заменять операнд, для некоторых команд ассемблера.

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,\n
CALL subr1

В этом случае выгоднее будет сделать операндом третьей
команды не #2121, а #1234 - тогда после компиляции получим две
идущие друг за другом одинаковые группы из 6 байтов, которые
сожмутся более эффективно. Так вот, компилятор может отслеживать
подобные ситуации и решать, какие значения будут принимать байты
операнда в ""-командах.
Ещё один вариант логики работы компилятора - не принимать
самому решения о том, какими значениями заполнять байты
операндов в ""-командах, а переложить эту задачу
непосредственно на упаковщик. При этом компилятор формирует два
выходных файла: в одном - откомпилированная программа, а в
другом указывается, какие байты этой программы могут принимать
произвольные значения. Упаковщик анализирует оба файла и
выбирает значения этих байтов так, чтобы размер программы после
упаковки был минимальным.
Hо такой упаковщик надо ещё написать! Или, если это
покажется более удобным, можно написать отдельную программу,
которая брала бы описанные выше два файла и выдавала один файл с
установленными значениями байтов, который и подаётся затем на
вход упаковщику. Hо, чтобы обеспечить наилучшее сжатие, эта
программа должна знать, как именно осуществляется упаковка.
Кстати, в [2] я писал о способе повышения сжатия музыкальных
модулей, основанном на автоматическом определении байтов модуля,
первоначальные значения которых не используются при проигрывании
(а значит, могут быть любыми). За счёт заполнения этих байтов
значением предыдущего байта и достигалось повышение степени
сжатия. Так вот, там можно точно так же выполнять замену не
просто на значение предыдущего байта, а так, чтобы сжатие было
наилучшим.


Литература
──────────

1. И.Рощин. "Оптимизация на примере intro 'Start'". "Радиомир.
Ваш компьютер" 7-10/2001.

2. И.Рощин. "Повышение степени сжатия музыкальных модулей".
"Радиомир. Ваш компьютер" 7/2002.

════════════════════════════════════════════════════════════════

Другие мои статьи об усовершенствовании ассемблера:

1. "Hесколько предложений по усовершенствованию ассемблера".
"ZX-Ревю" 7-10/1997.

════════════════════════════════════════════════

С уважением, Иван Рощин.




Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
ZX новости - новости из Ташкента и СНГ.
От редакции.

В этот день...   21 октября