© Иван Рощин, Москва
| ZXNet | : 500:95/462.53 |
| : bestview@mtu-net.ru | |
| WWW | : http://www.ivr.da.ru |
(«Радиолюбитель. Ваш
компьютер» 9/2000, 2/2001, под псевдонимом BV_Creator)
Дата последнего редактирования: 24.07.2003.
Процессор Z80, производимый компанией ZiLOG с 1976 года, используется,
помимо персональных компьютеров, во множестве других микропроцессорных
устройств. Так что эта статья будет полезна не только владельцам
ZX Spectrum. Она адресована,
Почему на ZX Spectrum основным языком программирования является ассемблер, а компиляторы Паскаля, Си и других ЯВУ практически не используются? Ведь на этих языках гораздо проще писать программы! Ответ прост: всё дело в том, что формируемая в процессе компиляции программа в машинных кодах получается медленнее и длиннее аналогичной программы на ассемблере. А при небольшом объёме памяти и низком быстродействии ZX Spectrum это имеет решающее значение.
А почему программа получается неоптимальной? Да потому, что компилятор
действует довольно примитивно. Надо ему, например, в программе поместить в
аккумулятор число 0 он и сформирует команду LD A,0. А любой хоть
Понятно, что компилятор тоже надо научить таким способам оптимизации. Тогда он, вполне возможно, сможет формировать даже более оптимальные программы, чем человек: ведь компилятор рассмотрит все возможные варианты оптимизации и выберет лучший, чего человек при всём желании не способен сделать. Ну кто, например, догадается, что если A=0, а надо получить A=6, то, при соответствующем значении некоторых флагов, можно обойтись одной лишь командой DAA?
Да, вот так мы и подошли вплотную к теме этой статьи. Речь пойдёт об оптимизации загрузки констант в регистры и регистровые пары Z80. Команды загрузки констант одни из наиболее часто встречающихся, и их оптимизация принесёт неплохой выигрыш в объёме и быстродействии.
Итак, что мы имеем. Пусть по ходу программы необходимо поместить некоторую константу в регистр или регистровую пару. Для этого в процессоре предусмотрены следующие команды, ни одна из которых не изменяет флаги:
| Команда | Длина, байтов | Время выполнения, тактов |
|---|---|---|
| LD A,n LD B,n LD C,n LD D,n LD E,n LD H,n LD L,n |
2 | 7 |
| LD XH,n LD XL,n LD YH,n LD YL,n |
3 | 11 |
| LD BC,nn LD DE,nn LD HL,nn |
3 | 10 |
| LD IX,nn LD IY,nn |
4 | 14 |
Команды LD XH,n, LD XL,n, LD YH,n, LD
Пусть к тому моменту, когда в регистр или в регистровую пару
должна быть помещена константа, нам известно хоть
Так вот, зная это, мы зачастую можем заменить перечисленные выше команды загрузки на более короткие и/или быстрые команды. Естественно, чем больше мы знаем, тем больше будет и возможностей для оптимизации.
Теперь подробно разберём, в каких случаях и как именно можно провести оптимизацию, каким получается выигрыш в длине и быстродействии.
Пример: надо поместить #23 в регистр D; знаем, что в регистре B также #23; помещаем в объектный код команду LD D,B.
Примечание: под словами «помещаем в объектный код команду » имеется в виду помещение в объектный код соответствующих данной команде машинных кодов. Отметим также, что компилятор может формировать не объектный, а непосредственно исполняемый код.
Пример 1: надо поместить #76 в регистр XH; знаем, что в регистре A также #76; помещаем в объектный код команду LD XH,A.
Пример 2: надо поместить #18 в регистр XL; знаем, что в регистре XH также #18; помещаем в объектный код команду LD XL,XH.
Пример 1: надо поместить #29 в регистр YH; знаем, что в регистре E также #29; помещаем в объектный код команду LD YH,E.
Пример 2: надо поместить #74 в регистр YL; знаем, что в регистре YH также #74; помещаем в объектный код команду LD YL,YH.
Пример: надо поместить #2574 в HL; знаем, что H=#25. Пробуем решить задачу оптимизации загрузки #74 в регистр L даже если это не получится, в объектном коде будет команда LD L,#74, что всё равно короче и быстрее, чем LD HL,#2574.
Внимание! Надо учитывать, что после выполнения первой команды LD значение регистра RRH изменится, и если R2 совпадает с RRH, то оптимизацию выполнить нельзя. Иногда в этом случае помогает перестановка команд LD местами.
Пример 1: надо поместить #1234 в HL; знаем, что A=#12, E=#34; помещаем в объектный код команды LD H,A: LD L,E.
Пример 2: надо поместить #7536 в BC; знаем, что A=#75, B=#36; помещаем в объектный код команды LD C,B: LD B,A (именно в этом порядке!).
Пример 3: надо поместить #8762 в DE; знаем, что D=#62, E=#87. Тем не менее, оптимизацию выполнить нельзя.
Пример 1: надо поместить в HL константу #1653. Известно, что DE=#1653 и DE больше не понадобится. Помещаем в объектный код команду EX DE,HL. Экономия составит 2 байта/6 тактов.
Пример 2: надо поместить в DE константу #8736. Известно, что HL=#8735, HL больше не понадобится, изменять флаги нельзя. Решая задачу оптимизации помещения #8736 в HL, получим, что это можно сделать командой INC HL за 1 байт/6 тактов. Это удовлетворяет ограничению в 2 байта/6 тактов. Помещаем в объектный код ещё команду EX DE,HL. Экономия составит 1 байт/0 тактов.
Пример 1: надо поместить в BC константу #8624; известно, что BC'=#8623, значения BC', DE', HL', DE, HL и регистра флагов больше не понадобятся. Помещаем в объектный код команду EXX. Решаем задачу помещения #8624 в BC с учётом того, что BC теперь равно #8263. Это можно сделать командой INC C за 1 байт/4 такта, что удовлетворяет ограничению в 2 байта/6 тактов. Экономия составит 1 байт/2 такта.
Пример 2: надо поместить в HL константу #7258; известно, что DE'=#7258, BC=#1289, BC'=#1289, значения DE', HL', DE больше не понадобятся. Помещаем в объектный код команду EXX. Решаем задачу помещения #7258 в HL с учётом того, что DE теперь равно #7258. Это можно сделать командой EX DE,HL за 1 байт/4 такта, что удовлетворяет ограничению в 2 байта/6 тактов. Экономия составит 1 байт/2 такта.
Что здесь и далее мы будем понимать под выражением «если флаги позволяют»? Вот что: либо если разрешено менять значения всех флагов, либо если можно менять значения не всех флагов или вообще нельзя менять флаги, но известно значение флагов до выполнения команды, и знаем, что после выполнения команды флаги, значения которых нельзя менять, не изменятся.
Кстати, в этом случае это касается и альтернативного регистра флагов.
Пример: надо поместить в аккумулятор #47; известно, что A'=#47; значение AF' больше не понадобится; значение флага C нельзя менять, но известно, что он одинаково установлен как в основном, так и в альтернативном флаговом регистре. Помещаем в объектный код команду EX AF,AF'.
Пример 1: надо поместить в аккумулятор 0, сохранив при этом значение флага N (известно, что N=1) и не сохраняя значение всех других флагов. Помещаем в объектный код команду SUB A.
Пример 2: надо поместить в аккумулятор #25; известно, что флаг CY=1; сохранять значения флагов не нужно. Известно, что A=#20, B=4. Помещаем в объектный код команду ADC A,B.
Пример 1: надо поместить в аккумулятор #55; знаем, что A=#AA; знаем, что надо сохранить значения флагов CY и Z. Помещаем в объектный код команду CPL.
Пример 2: надо поместить в аккумулятор 6; знаем, что A=0; знаем, что флаг H=1, флаг N=0, и что сохранять значения флагов не нужно. Помещаем в объектный код команду DAA.
Пример 1: надо поместить #23 в регистр D, а в этом регистре находится #22. Помещаем в объектный код команду INC D.
Пример 2: надо поместить #FF в регистр XH, а в этом регистре находится 0. Помещаем в объектный код команду DEC XH.
Пример: надо поместить #13FF в BC, а там находится #1400. Помещаем в объектный код команду DEC BC.
Пример: надо поместить #32 в C; знаем, что C=#31 и изменять значения флагов нельзя. Помещаем в объектный код команду INC BC.
Пример 1: надо поместить #82 в D; знаем, что DE=#81FF, значение E больше не понадобится, флаги изменять нельзя. Помещаем в объектный код команду INC DE.
Пример 2: надо поместить #40 в H; знаем, что HL=#2000 и сохранять флаги не нужно. Помещаем в объектный код команду ADD HL,HL.
Пример 3: надо поместить #12 в XL; знаем, что IX=#5309, значение XH больше не понадобится, сохранять флаги не нужно. Помещаем в объектный код команду ADD IX,IX.
Пример 4: надо поместить #25 в H; знаем, что HL=#1000, BC=#1500; можно изменять флаги, кроме Z. Помещаем в объектный код команду ADD HL,BC.
Пример: надо поместить #70 в XH; знаем, что XH=#20, SP=#5000; можно менять значения всех флагов, кроме Z и S. Помещаем в объектный код команду ADD IX,SP.
Пример 1: надо поместить в L #26; знаем, что L=#10, BC=#0016, сохранять флаги не нужно. Помещаем в объектный код команду ADD HL,BC.
Пример 2: надо поместить в YL #70; знаем, что YL=#74, DE=#FFFC, сохранять флаги не нужно. Помещаем в объектный код команду ADD IY,DE.
Пример 1: надо поместить в HL #AAAA; знаем, что HL=#5555, сохранять флаги не нужно. Помещаем в объектный код команду ADD HL,HL.
Пример 2: надо поместить в IX #7624; знаем, что IX=#1211, DE=#6413, сохранять флаги не нужно. Помещаем в объектный код команду ADD IX,DE.
Пример 1: надо поместить в HL 0; знаем, что CY=0 и сохранять флаги не нужно. Помещаем в объектный код команду SBC HL,HL.
Пример 2: надо поместить в HL #2299; знаем, что HL=#2266, BC=#0032, CY=1 и сохранять флаги не нужно. Помещаем в объектный код команду ADC HL,BC.
Пример 1: надо поместить в HL #8080; знаем, что HL=#2020 и сохранять флаги не нужно. Помещаем в объектный код команды ADD HL,HL: ADD HL,HL.
Пример 2: надо поместить в HL #8081; знаем, что HL=#4040 и сохранять флаги не нужно. Помещаем в объектный код команды ADD HL,HL: INC L.
Пример 3: надо поместить в HL #8082; знаем, что HL=#4040 и сохранять флаги не нужно. Помещаем в объектный код команды INC L: ADD HL,HL.
Пример 4: надо поместить в HL #1234; знаем, что HL=#1111, DE=#0111, BC=#0012 и сохранять флаги не нужно. Помещаем в объектный код команды ADD HL,DE: ADD HL,BC.
Пример 1: надо поместить в HL #1234; знаем, что HL=#1123, DE=#0111, флаг переноса установлен и изменять его нельзя. Помещаем в объектный код команды ADD HL,DE: CCF.
Пример 2: надо поместить в IY 0; знаем, что IY=#FFF0, BC=#0010, флаг переноса сброшен и изменять его нельзя. Помещаем в объектный код команды ADD IY,BC: CCF.
При оптимизации необходимо пробовать применить каждое из двадцати трёх правил, перечисленных выше, и если подходят сразу несколько правил, выбрать то, которое обеспечивает наилучший результат. Если от оптимизируемой программы требуется максимальное быстродействие, придётся отбросить такие варианты оптимизации, которые уменьшают длину программы за счёт увеличения времени загрузки константы.
Обратите внимание на правила 9 и 10. При применении каждого из них приходится изменить задачу оптимизации (назовём эту изменённую задачу подзадачей) и решить её (для этого заново применив к ней все правила). При решении подзадачи необходимо исключить из списка правил то правило (9 или 10), при применении которого эта подзадача возникла. Это нужно, чтобы избежать зацикливания.
При написании этой статьи у меня появились некоторые мысли, впрямую не относящиеся к её теме, однако косвенно связанные с ней. Попробую их изложить.
Может быть,
И ещё коснусь вопроса оптимизации. Хотелось бы, чтобы в Интернете был сайт,
специально посвящённый оптимизации кода Z80, чтобы программисты со всего мира
могли воспользоваться размещённой там информацией (и добавить свою). Не
знаю возможно,
|
Другие мои статьи о различных приёмах оптимизации при программировании на ассемблере Z80:
|