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.