Several optimization techniques
ZXNet echo conference «code.zx»
From Ivan Roshin → To All 5 September 2005
Hello, All!
═══════════════════ z80_opt.txt ══════════════════
(c) Ivan Roshchin, Moscow
ZXNet: 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru
Several optimization techniques
═════════════════════════════
("Radiomir. Your computer" 8/2005)
This article describes some optimization techniques for
programming in Z80 assembler. Perhaps I'm not the first
came up with (since they are quite obvious), but I don’t
I had to read about these techniques somewhere or see examples
their use, that's why I decided to talk about them. Maybe
someone will need it.
1. If flag C=1, place the constant n in A, otherwise reset A.
SBC A,A ;If flag C=1, then we get A=#FF, otherwise A=0.
AND n ;#FF will turn into n, but 0 will not change.
2. If flag C=1, place constant n1 in A, otherwise - constant
n2.
SBC A,A ;If flag C=1, then we get A=#FF, otherwise A=0.
AND n1-n2 ;#FF will turn into n1-n2, but 0 will not change.
ADD A,n2 ;We get n1 or n2, respectively.
3. Let there be a subroutine at the end of which there are two identical
fragments, each length is x bytes (x>=4). Here's an example:
..........
LD A,(HL)
INC HL
LD H,(HL)
LD L,A
LD A,(HL)
INC HL
LD H,(HL)
LD L,A
RETOptimization using cycle organization (if you put
before the repeated fragment the command LD B,2, and after
fragment - command DJNZ M1, where M1 is the address of the first command
fragment) will give a gain of x-4 bytes (and if the value of register B
cannot be changed, then you will have to organize the cycle somehow differently,
which can further reduce the winnings). For the above example,
where x=4, no winnings are obtained:
........
LD B,2
M1 LD A,(HL)
INC HL
LD H,(HL)
LD L,A
DJNZ M1
RET
But there is a more elegant optimization method that provides
gain x-3 bytes (i.e. one byte more): just put in front
at the beginning of the repeating fragment, a 3-byte command CALL $+3:
........
CALL $+3
LD A,(HL)
INC HL
LD H,(HL)
LD L,A
RET
Let me remind you that $ is the address of the current command. Since the length
CALL commands are 3 bytes, then $+3 will be the address of the next command.
Thus, the command CALL $+3 will transfer control to the first
command of a repeating fragment, and it will be pushed onto the stack
the address of this command. After the fragment is executed for the first time
times, by command RET control will be transferred to the saved
on the stack the address of the beginning of this fragment. So the fragment
will be executed a second time, which is what is required. Then when executingRET command will exit the subroutine.
It is not difficult to guess that if you need to perform a repetitive
fragment four times, then to do this it is enough to put it in front
it starts with two commands CALL $+3; if you need to do eight
once, then three such teams, etc. But it might be more profitable here
optimization using loop organization.
And one more thing. If in a subroutine before a repeating fragment
there are other commands and you can choose the compilation address of this
subroutines, then if you're lucky, additional
optimization - due to the fact that the jump address in the CALL command
(or the high byte of this address) will simultaneously be
command codes (or command code) located before
repeating fragment. The winnings could be one or two
byte.
I’ll explain with examples (let’s take the already discussed one as a basis.
earlier example). First, consider the case when the payoff is
two bytes. This is possible if before the repeating fragment
There is either a two-byte command or two one-byte ones. For
example, let these be two one-byte commands: LD H,B (code
#60) and LD L,C (code #69). Then, choosing the compilation address
subroutines, you can do this:
........
#695F: DB #CD ;Command code CALL nn.
#6960: LD H,B ;These two commands, in addition to their direct
LD L,C ;destinations form the address of the called;subroutines - #6960 (low byte first
;addresses, then senior).
#6962: LD A,(HL)
INC HL
LD H,(HL)
LD L,A
RET
When executing the CALL command located at address #695F
an address 3 bytes larger will be pushed onto the stack (i.e. #6962),
and the transition will occur to address #6960. The commands LD H,B will be executed
and LD L,C, and then the first time the repeating
fragment. After executing the RET command, the transition will occur
to the address #6962 previously stored on the stack, repeating
the fragment will be executed a second time, and then after the second
execution of the RET command will exit the subroutine.
By the way, if address #6960 didn’t work for some reason,
it would be possible to change the order of the commands: first LD L, C, and then
her - LD H,B. They would then form address #6069. Or you can
would be to replace these two commands with another two: PUSH BC (code
#C5) and POP HL (code #E1), and they would form the address #E1C5.
Now let's look at another case. Let's assume that before
the beginning of the fragment is the three-byte command LD BC, #1234 and
single-byte command EXX (code #D9). Save two bytes here already
it won’t work, but one byte is still possible if you select the address
compilation so that in the fragment below the command EXX
was located at an address like #D9xx.
........
LD BC,#1234
DB #CD ;Command code CALL nn.DB #xx ;Low byte of the address.
#D9xx: EXX ;High byte of the address.
LD A,(HL)
INC HL
LD H,(HL)
LD L,A
RET
4. Let a one-byte variable be stored in memory at address nn,
equal to 0 or 1, and you need to change it so that instead of 0 it becomes
1 and vice versa. Then instead of this:
LD HL,nn ;3 bytes, 10 clock cycles.
LD A,(HL) ;1 byte, 7 clock cycles.
XOR 1 ;2 bytes, 7 clock cycles.
LD (HL),A ;1 byte, 7 clock cycles.
;Total: 7 bytes, 31 clock cycles.
you can do this:
LD HL,nn ;3 bytes, 10 clock cycles.
INC (HL) ;1 byte, 11 clock cycles.
RES 1,(HL) ;2 bytes, 15 clock cycles.
;Total: 6 bytes, 36 clock cycles.
and it will be a byte shorter (but 5 clock cycles slower!). Also
in this case the battery does not change.
Likewise, if the variable being changed is in one of
registers B, C, D, E, H, L (let’s denote it as r), then instead of this:
LD A,r ;1 byte, 4 clock cycles.
XOR 1 ;2 bytes, 7 clock cycles.
LD r,A ;1 byte, 4 clock cycles.
;Total: 4 bytes, 15 clock cycles.
it's more profitable like this:
INC r ;1 byte, 4 clock cycles.
RES 1,r ;2 bytes, 8 clock cycles.
;Total: 3 bytes, 12 clock cycles.
This is shorter by a byte, and faster by 3 clock cycles, and does not change
battery.
* * *And one more optimization technique. I thought of it myself, but
then he began to check whether there were any examples of it
used by someone else, and it turned out - yes, they do occur.
But I still think it’s necessary to talk about it, because... read about something like this
I didn't have to.
5. Add A with the contents of register pair BC, DE or HL
(let’s call it r1r2) and place the result in the same or another
from these register pairs (let's denote it r3r4).
ADD A,r2 ;Received ml. sum byte and carry sign.
LD r4,A ;Save ml. sum byte.
ADC A,r1 ;A=ml. sum byte + carry sign + r1.
SUB r4 ;A=transfer sign + r1 = st. sum byte.
LD r3,A ;Save art. sum byte.
If the result needs to be obtained in HL, they usually do this (example
for the case when the second term is in BC):
LD H,0 ;2 bytes, 7 clock cycles.
LD L,A ;1 byte, 4 clock cycles.
ADD HL,BC ;1 byte, 11 clock cycles.
;Total: 4 bytes, 22 clock cycles.
It's one byte shorter. But the method described at the beginning for two
beat faster! See:
ADD A,C ;1 byte, 4 clock cycles.
LD L,A ;1 byte, 4 clock cycles.
ADC A,B ;1 byte, 4 clock cycles.
SUB L ;1 byte, 4 clock cycles.
LD H,A ;1 byte, 4 clock cycles.
;Total: 5 bytes, 20 clock cycles.
But sometimes maximum speed is required evenby increasing the length of the program.
I note that from the point of view of the influence on flags, these two methods
are not equivalent.
════════════════════════════════ ════════════════════════════════
My other articles are about various optimization techniques for
Z80 assembly language programming:
1. "About relocating programs." "ZX-Review" 5-6/1997.
2. "Z80: optimization of loading constants into registers."
"Radio amateur. Your computer" 9/2000, 2/2001 (under
alias BV_Creator).
3. "More about programming arithmetic operations."
"Radio amateur. Your computer" 12/2000, 1-4/2001.
4. "The effect of the OUTD command on the carry flag." "Radio amateur. Yours
computer" 5/2001, "Radiomir. Your computer" 8/2003 (under
alias BV_Creator).
5. "Optimization using the example of intro 'Start'". "Radiomir. Yours
computer" 7-10/2001.
6. "Manager for calling subroutines from various memory banks."
"Radiomir. Your computer" 12/2001, 2/2002, 4/2002.
7. "Improving the compression of Z80 assembler programs." "Radiomir. Yours
computer" 4/2003.
8. "Procedure for comparing strings in Z80 assembler." "Radiomir. Yours
computer" 6/2003.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.