home *** CD-ROM | disk | FTP | other *** search
/ ftp.barnyard.co.uk / 2015.02.ftp.barnyard.co.uk.tar / ftp.barnyard.co.uk / cpm / walnut-creek-CDROM / JSAGE / ZSUS / TCJ / TCJ39LH.WS < prev    next >
Text File  |  2000-06-30  |  34KB  |  674 lines

  1. [Note by Jay Sage:  I am always actively recruiting new columnists for TCJ. ì
  2. Rick Charnes, whose excellent articles are now appearing in every issue, wasì
  3. my first success.  With this issue I would like to introduce the second, Leeì
  4. Hart.  Lee has been carrying forward the development of Poor Personì
  5. Software's Write-Hand-Man, the 8-bit "SideKick".  That kind of program, likeì
  6. BGii, cannot tolerate wasteful coding, and in this, the first of twoì
  7. articles, Lee shares with us some of his tricks.]
  8.  
  9.  
  10.                          PROGRAMMING FOR PERFORMANCE
  11.                                 by Lee A. Hart
  12.                         The Computer Journal, Issue 39
  13.                           Reproduced with permission
  14.                            of author and publisher
  15.  
  16.  
  17.  
  18.  
  19.    Over the years, the ancient masters of the software arts have ì
  20. meticulously crafted the tools of structured programming.  They haveì
  21. eloquently preached the virtues to body and soul that come from writingì
  22. clean, healthy code, free from the evils of self-modifying code or theì
  23. dreaded GOTO.
  24.  
  25.    Many programmers have seen the light.  They write exclusively inì
  26. structured high-level languages, and avoid BASIC as if it carried AIDS. ì
  27. Assembly language is just that unreadable stuff the compiler generates as anì
  28. intermediate step before linking.  Memory and processor speed are viewed asì
  29. infinite resources.  Who cares if it takes 100K for a pop-up calculatorì
  30. program?  And if it's not fast enough, use turbo mode, or a 386.
  31.  
  32.    But a REAL pocket calculator doesn't have a 16-bit processor, or 100K ofì
  33. RAM; it typically runs a primitive 4-bit CPU at 1 MHz or less, with perhapsì
  34. 2K of memory.  Yet it can out-perform a PC clone having 10 times the speedì
  35. and memory!
  36.  
  37.    How can this be?  Special hardware?  Tricky instruction sets?  On theì
  38. contrary; CPU registers and instructions have instead been removed to cutì
  39. cost.  No; the surprising performance comes from clever, efficientì
  40. programming with an extreme attention to detail.  Such techniques areì
  41. essential to the success of every high-volume micro-based product.  But theyì
  42. aren't widely known and so are rarely applied to general-purposeì
  43. microcomputers. 
  44.  
  45.    Suppose your micro doesn't provide the luxury of unlimited (or evenì
  46. adequate) resources.  Your program absolutely has to fit in a certain space,ì
  47. such as a ROM.  You're stuck with a slow CPU but must handle a hardwareì
  48. device with particularly severe timing requirements.  Your C compiler justì
  49. turned out a program that misses the mark by a megabyte.  Don't give up!ì
  50. I'll show you some techniques that are particularly effective at "runningì
  51. light without overbyte," as Dr. Dobbs used to say.
  52. è   I'll demonstrate these techniques with the Z80.  With over 30 millionì
  53. sold last year alone, it remains the number-one-selling micro and is widelyì
  54. used in cost-effective designs when performance counts.  However, theì
  55. principles used apply to almost any microcomputer.
  56.  
  57. In the beginning
  58.  
  59.    Novice Z80 programmers soon spot peculiarities in the instruction set;ì
  60. arcane rules restrict data movement between registers.  For instance, theì
  61. stack pointer can be loaded but not examined.  Flags are not setì
  62. automatically and must be explicitly updated by a math or logicalì
  63. instruction.  The carry flag can be set or inverted but not reset.  Of theì
  64. six flags, only four can be tested by jumps and calls (and only two byì
  65. relative jumps). 
  66.  
  67.    These limitations are no accident.  They represent an artful compromiseì
  68. between cost, complexity, performance, and compatibility with the earlierì
  69. 8080 instruction set.  To get the most out of any micro, you must discoverì
  70. how its designer expected you to use the architecture.  Get "inside" hisì
  71. head; become part of the machine.
  72.  
  73.    The Z80 is register-oriented; it manipulates data in registersì
  74. efficiently but deals rather clumsily with memory.  Registers areì
  75. specialized, with each having an intended purpose.  Here are some rules I'veì
  76. found useful:
  77.  
  78.     A = Accumulator: first choice for 8-bit data; best selection of ì
  79. load/store instructions; source and destination for most math, logical, andì
  80. comparisons.
  81.  
  82.     HL = High/Low address: first choice for 16-bit data/addresses; sourceì
  83. and destination for 16-bit math; second choice for 8-bit data; pointer whenì
  84. one math/logical/compare operand is in memory; source address for blockì
  85. operations.
  86.  
  87.     DE = DEstination: second choice for 16-bit data/addresses; third choiceì
  88. for 8-bit data; destination address for block operations.
  89.  
  90.     BC = Byte Counter: third choice for 16-bit data/addresses; I/O portì
  91. addreses; 8/16 bit counter for loops and block operations.  
  92.  
  93.     F = Flag byte (6 bits used): updated by math/logical/compare ì
  94. instructions; Zero, Carry, Sign, and Parity tested by conditional jumps,ì
  95. calls, or returns; Zero and Carry by relative jumps; block operations useì
  96. Parity; bit tests use Zero; shifts use Carry; only decimal adjust testsì
  97. Half-carry and Add/Subtract flags.
  98.  
  99.     A',BC',DE',F',HL' = twins of A, BC, DE, F, HL; can be quickly swappedì
  100. with main set; use for frequently used variables, fast interrupt handlers,ì
  101. task switching.
  102.  
  103.     R = Refresh counter for dynamic RAM: also counts instructions forìèdiagnostics, debuggers, copy-protection schemes; pseudorandom numberì
  104. generator; interrupt detection.
  105.  
  106.     I = Interrupt vector: page address for interrupts in mode 2; otherwise,ì
  107. an extra 8-bit register that updates flags when read.
  108.  
  109.     IX,IY = Index registers X and Y: Two 16-bit registers, used like HL asì
  110. an indirect memory pointer, except instructions can include a relativeì
  111. offset.
  112.  
  113.     SP = Stack Pointer: 16-bit memory pointer for LIFO (last-in first-out)ì
  114. stack to hold interrupt and subroutine return address, pushed/poppedì
  115. register data; stack-oriented data structures.
  116.  
  117.    Naturally, some instructions get used a lot more than others.  But ì
  118. frequency-of-use studies reveal that many programs NEVER use large portionsì
  119. of the instruction set.  Sometimes there are good reasons, like sticking toì
  120. 8080 opcodes so your code runs on an 8080/8085/V20 etc.  More often theì
  121. programmer is simply unfamiliar with the entire instruction set, and soì
  122. restricts himself to what he knows.
  123.  
  124.    This is fine for noncritical uses but suicidal when performance counts.ì
  125. It's like running a racehorse with a gag in its throat.  Take some time toì
  126. go over the entire instruction set, one by one.  Devise an example for eachì
  127. instruction that puts it to good use.  I only know of nine turkeys with noì
  128. use besides "trick" NOPs (can you find them?).
  129.  
  130.    Here's a routine that might be written by a rather inept programmer (orì
  131. an unusually efficient compiler).  It outputs a string of characters endingì
  132. in 0 to the console.  It generally follows good programming practices; it'sì
  133. well structured, has clearly-defined entry and exit points, and carefullyì
  134. saves and restores all registers used.
  135.  
  136.         ...
  137.     ld    de,string    ; point to message
  138.     call    outstr        ; output it
  139.     ...
  140.  
  141. outstr:    push    af        ; save registers
  142.     push    bc
  143.     push    de
  144.     push    hl
  145.     ld    a,(de)        ; get next character
  146.     cp    0        ; compare it to 0
  147.     jp    z,outend    ; if not last char,
  148.     push    de        ; ..save registers
  149.     ld    e,a
  150.     ld    c,conout    ; output character to console
  151.     call    bdos
  152.     pop    de        ; restore registers
  153.     inc    de        ; advance to next
  154.     jp    outstr        ; repeat until doneèoutend:    pop    hl        ; else 0 is last char
  155.     pop    de
  156.     pop    bc        ; restore registers
  157.     pop    af
  158.     ret            ; return
  159.  
  160. string:    db    'message'    ; display our message
  161.     db    0        ; end of string marker
  162.  
  163.    Now let's see how it can be improved.  First, note that over half theì
  164. instructions are PUSHes or POPs.  This is the consequence of saving everyì
  165. register before use, a common compiler strategy.  Though safe and simple,ì
  166. it's the single worst performance-killer I know.
  167.  
  168.    The alternative is to push/pop only as necessary.  This is easier saidì
  169. than done; miss one, and you've got a nasty bug to find.  A good strategyì
  170. helps.  I initially define my routines to minimize the registers used; onlyì
  171. push/pop as needed within the routine itself; and restore nothing on exit. ì
  172. In OUTSTR, this eliminates all but the PUSH DE/POP DE around the CALL BDOS.
  173.  
  174.    This shifts the save/restore burden to the calling routine.  Since theì
  175. caller also follows the rule of minimal register usage and push/pops only asì
  176. necessary, it will probably not push/pop as many registers; thus we haveì
  177. increased speed by eliminating redundant push/pops.  We have also made itì
  178. explicitly clear which registers a caller really needs preserved.
  179.  
  180.    Now I move the remaining push/pops to the called routines to save memory. ì
  181. If every caller saves a particular register, it obviously should beì
  182. saved/restored by the subroutine itself.  If two or more callers save it,ì
  183. speed is the deciding factor; preserve that register in the subroutine ifì
  184. the extra overhead is not a problem for callers that don't need thatì
  185. register preserved.
  186.  
  187.    Push/pops are sloooww; at 21 to 29 T-states per pair, they make wonderfulì
  188. low-byte time killers.  If possible, either use, or save to, a register thatì
  189. isn't killed by the called routine.  In our example, try IX or IY instead ofì
  190. DE; the index registers aren't trashed by the BDOS call (except, see Jayì
  191. Sage's column.  Ed).  This saves 5 T-states/loop but adds 2 bytes (seeì
  192. why?).  The instruction EX DE,HL (8 T-states per pair) is often useful, butì
  193. not here; the BIOS eats both HL and DE.  The ultimate speed demon is a fast-n-drastic pair of EXX instructions to replace the PUSH DE/POP DE.  They saveì
  194. 13 T-states with no size increase, and even preserve BC so we don't have toì
  195. reload it for every loop.
  196.  
  197. Comparisons
  198.  
  199.    A CP 0 instruction was used to test for 0, an obvious choice.  But itì
  200. takes 2 bytes and 7 T-states to execute.  The Z80's Zero flag makes theì
  201. special case of testing for zero easy; all we have to do is update the flagsì
  202. to match the byte loaded.  This is most easily done with an OR Aì
  203. instruction, which takes only 1 byte and 4 T-states.  You'll find this trickì
  204. often in Z80 code.
  205. è   Note that OR A has no effect on A; we just used it to update the flagsì
  206. because it's smaller and faster than CP 0.  This illustrates a basicì
  207. principle of assembly languages; the side effects of an instruction areì
  208. often more important than the main effect.  Here are some other not-so-obvious instructions:
  209.  
  210.      and  a     ; update flags and clear Carry
  211.      xor  a     ; set A=0, update flags, P/V flag=1
  212.      sub  a     ; same, but P/V flag=0
  213.      sbc  a,a   ; set all bits in A to Carry (00 or FF)
  214.      add  a,a   ; A*2, or shift A left & set lsb=0
  215.      add  hl,hl ; HL*2, or shift HL left & set lsb=0
  216.      adc  hl,hl ; shift HL left & lsb=Carry
  217.      sbc  hl,hl ; set all bits in HL to Carry (0000 or FFFF)
  218.      ld   hl,0  ; \_load SP into HL so it can be examined
  219.      add  hl,sp ; /
  220.  
  221.    Using DE as the string pointer is a weak choice.  It forces us to loadì
  222. the character into A, then move it to E.  If we use HL, IX, or IY instead,ì
  223. we can load E directly and save a byte.  But this makes it harder to testì
  224. for 0.
  225.  
  226.    An INC E, DEC E updates the Z flag without changing E.  Or mark the endì
  227. of the string with 80h, and use BIT 7,E to test for end.  Both are asì
  228. efficient as the OR A trick but don't need A.  If you are REALLY desperate,ì
  229. add 1 to every byte in the string, so a single DEC E restores the characterì
  230. and sets the Z flag; kinky, but short and fast. 
  231.  
  232. Jumps
  233.  
  234.    This example used 3-byte absolute jump instructions.  We can save memoryì
  235. by using the Z80's 2-byte relative jumps instead; each use saves a byte. ì
  236. Since jumps are among the most common instructions, this adds up fast.
  237.  
  238.    Relative jumps have a limited range, so it pays to arrange your codeì
  239. carefully to maximize their use.  I've found that about half the jumps in aì
  240. well structured program can be relative.  When most of the jumps are out ofì
  241. range, it's often a sign of structural weaknesses, "spaghetti-code" orì
  242. excessively complex subroutines. 
  243.  
  244.    How about execution speed?  An absolute jump always takes 10 T-states; aì
  245. relative jump takes 12 to jump, or 7 to continue.  So if speed counts, useì
  246. absolute jumps when the branch is normally taken, and relative jumps when itì
  247. is not.  In the example, this means changing the JP Z,OUTEND to JR Z,OUTENDì
  248. but keeping the JP at the end. 
  249.  
  250.    But wait a minute!  The JR Z,OUTEND merely jumps to the RET at the end ofì
  251. the subroutine.  It would be more efficient still to replace it with RET Z,ì
  252. a 1-byte conditional return that is only 5 T-states if the return is notì
  253. taken.  This illustrates another difference between assembler and high-levelì
  254. languages; entry and exit points are often not at the beginning and end of aì
  255. routine.
  256. è   We can speed up unconditional jumps within a loop.  On entry, load HLì
  257. with the start address of the loop, and replace JP LABEL by JP (HL).  Itì
  258. takes 1 byte and 4 T-states, saving 6 T-states per loop.  This scheme costsì
  259. us a byte (+3 to set HL; -2 for JP (HL)).  But if used more than once in theì
  260. routine, we save 2 bytes per occurrence.  If HL is unavailable (as is theì
  261. case here; the BDOS trashes it), IX or IY can be used instead.  However, theì
  262. JP (IX) and JP (IY) instructions take 2 bytes and 8 T-states, making the ì
  263. savings marginal.
  264.  
  265.    Can we do better yet?  Yes, if we carefully rethink the structure of ourì
  266. program.  Notice it has two jump instructions per loop; yet only one test isì
  267. performed (test for 0).  This is a hint that one conditional jump should beì
  268. all we need.  Think of the instructions in the loop as links in a chain.ì
  269. Rotate the chain to put the test-for-0 link at the bottom, and LD C,CONOUTì
  270. on top (which we'll label OUTNXT).  The JP OUTSTR is now unnecessary, andì
  271. can be removed.  JP NZ,OUTNXT performs the test and loops until 0 (remember,ì
  272. absolute for speed, relative for size).  The entry point is still OUTSTR,ì
  273. though (horrors!) it's now in the middle of the routine.
  274.  
  275.    We've also made a subtle change in the logic.  Presumably we wouldn'tì
  276. call OUTSTR unless there was at least one character to output.  But whatì
  277. would happen if we did?
  278.  
  279.    Another way is to use DJNZ to close the loop.  Make the first byte of theì
  280. string its length (1-256).  Load this value into B as part of theì
  281. initialization.  The resulting program takes 34 T-states per loop (notì
  282. counting the CALL). 
  283.  
  284.    STILL faster?  OK, you twisted my arm.  If you're absolutely sure theì
  285. string won't cross a page boundary, you can use INC L instead of INC HL toì
  286. save 2 T-states.  The 8-bit INC/DEC instructions are faster than theirì
  287. 16-bit counterparts, but should only be used if you're positive the addressì
  288. will never require a carry.  This brings us to 32 T-states/loop, which isì
  289. the best I can do within this routine itself.  Or can you do better?
  290.  
  291.         ...
  292.         ld     hl,string           ; point to message
  293.         call   outstr              ; output it
  294.         ...
  295.  
  296. outstr: ld     b,(hl)              ; get length of message
  297.         ld     c,conout            ; output to console
  298.         ld     e,(hl)              ; get 1st char
  299. outnxt: exx                        ; save registers,
  300.         call   bdos                ;   output char,
  301.         exx                        ;   and restore
  302.         inc    hl                  ; advance to next
  303.         ld     e,(hl)              ; get next character
  304.         djnz   z,outnxt            ; loop until end
  305.         ret
  306.  
  307. string: db     strend - strbeg     ; message lengthèstrbeg:    db     'message'           ; message itself
  308. strend:
  309.  
  310. Parameter Passing
  311.  
  312.    In the above example, parameters were passed to the subroutine viaì
  313. registers (string address in HL).  This is fast and easy, but each call toì
  314. OUTSTR takes 6 bytes.  Now let's look at methods that save memory at theì
  315. expense of speed.
  316.  
  317.    Parameters can be passed to a subroutine as "data" bytes immediatelyì
  318. following the CALL.  Let's define the two bytes after CALL OUTSTR as theì
  319. address of the string.  The following code then picks up this pointer,ì
  320. saving us a byte per call.  The penalty is in making OUTSTR 4 bytes longerì
  321. and 38 T-states/loop slower; thus it doesn't pay until we use it 5 or moreì
  322. times.
  323.  
  324.         ...
  325.         call   outstr              ; output message
  326.         dw     string              ; beginning here
  327.         ...
  328.  
  329. outstr: pop    hl                  ; get pointer to "DW STRING"
  330.         ld     e,(hl)              ;   E=low byte of string addr
  331.         inc    hl
  332.         ld     d,(hl)              ;   D=high byte of string addr            
  333.         inc    hl                  ; skip over "DW STRING" & push
  334.         push   hl                  ;   corrected return address
  335.  
  336. outnxt: ld     a,(de)              ; get next character
  337.         or     a                   ; if 0,
  338.         ret    z                   ;   all done, return
  339.         push   de
  340.         ld     e,a
  341.         ld     c,conout            ; output character to console
  342.         call   bdos        
  343.         pop    de
  344.         inc    de                  ; advance to next
  345.         jr     outnxt
  346.  
  347.    We also had to rethink our choice of registers.  If we tried to use HL orì
  348. IX as the string pointer, OUTSTR would have been larger and slower (try itì
  349. yourself).  This demonstrates the consequences of inappropriate registerì
  350. choices.
  351.  
  352.    The more parameters that must be passed, the more efficient thisì
  353. technique becomes.  A further refinement is to put the string itselfì
  354. immediately after CALL.  This saves an additional two bytes per call, andì
  355. shortens OUTSTR by 6 bytes.   
  356.  
  357.         ...
  358.         call   outstr              ; output messageè        db     'message',0         ; which immediately follows
  359.         ...
  360.  
  361. outstr: pop    de                  ; get pointer to message
  362.         ld     a,(de)              ; get next character
  363.         inc    de                  ; advance to next
  364.         push   de                  ; & save as return address
  365.         or     a                   ; if char=0, all done
  366.         ret    z                   ;   pointer is return addr
  367.         ld     e,a
  368.         ld     c,conout            ; else output char to console
  369.         call   bdos        
  370.         jr     outstr              ;   & repeat
  371.  
  372. Constants and Variables
  373.  
  374.    Constants and variables are part of every program.  Constants are usuallyì
  375. embedded within the program itself, as "immediate" bytes.  Variables on theì
  376. other hand are usually separated, grouped into a common region perhaps atì
  377. the end of the program.  This makes sense for programs in ROM, where theì
  378. variables obviously must be stored elsewhere.  But it is not a requirementì
  379. for programs in RAM.
  380.  
  381.    If your program executes from RAM, performance can be improved byì
  382. treating variables as in-line constants; storage for the variable is in theì
  383. last byte (or two) of an immediate instruction.  For example, here is aì
  384. routine that creates a new stack, toggles a variable FLAG between twoì
  385. states, and then restores the original stack:
  386.  
  387. toggle: ld     (stack),sp          ; save old stack pointer 
  388.         ld     sp,mystack          ; setup my stack
  389.         ld     a,(flag)            ; get Yes/No flag
  390.         cp     'Y'                 ; if "Y",     
  391.         ld     a,'N'               ;    set it to "N"
  392.         jr     z,setno             ; else "N",
  393.         ld     a,'Y'               ;    set it to "Y"
  394. setno:  ld     (flag),a            ; save new state
  395.         ld     sp,(stack)          ; restore stack pointer
  396.         ret
  397.  
  398. stack:  dw     0                   ; old stack pointer
  399. flag:   db     'Y'                 ; value of flag
  400.  
  401.    The LD A,(FLAG) instruction takes 13 T-states and 4 bytes of RAM (3 forì
  402. the instruction, 1 to store FLAG).  It can be replaced by LD A,'Y' where 'Y'ì
  403. is the initial value of the variable FLAG, the 2nd byte of the instruction.ì
  404. Speed and memory are improved 2:1, to 7 T-states and 2 bytes respectively.
  405.  
  406.    It works for 16-bit variables as well.  Replace LD SP,(STACK) with LDì
  407. SP,0 where 0 is a placeholder for the 2-byte variable STACK.  This saves 3ì
  408. bytes and 10 T-states.
  409. ètoggle: ld     (stack+1),sp        ; save old stack pointer 
  410.         ld     sp,mystack          ; setup my stack
  411. flag:   ld     a,'Y'               ; get Y/N flag (byte 2=var)
  412.         cp     'Y'                 ; if "Y",     
  413.         ld     a,'N'               ;    set it to "N"
  414.         jr     z,setno             ; else "N",
  415.         ld     a,'Y'               ;    set it to "Y"
  416. setno:  ld     (flag+1),a          ; save new state
  417. stack:  ld     sp,0                ; restore stack (byte 2,3=var)
  418.         ret
  419.  
  420.    There is another advantage to this technique -- versatility.  Anyì
  421. immediate-mode instruction can have variable data; loads, math, compares,ì
  422. logical, even jumps and calls.  Try changing our first example so a variableì
  423. OUTDEV selects the output device; console or printer.  Now see how simple itì
  424. is if OUTDEV is the 2nd byte of the LD C,CONOUT instruction.
  425.  
  426.    It even creates new instructions.  For instance, the Z80's indexed ì
  427. instructions don't allow a variable offset.  This makes it awkward to loadì
  428. the "n"th byte of a table, where we would like LD A,(IX+b) where "b" is aì
  429. variable.  But it can be done if the variable offset is stored in the lastì
  430. byte of the indexed instruction itself. 
  431.  
  432.    Storing variables in the address field of a jump or call instruction canì
  433. do some weird and wonderful things.  There is no faster way to perform aì
  434. conditional branch based on a variable.  But remember you are treading onì
  435. the thin ice of self-modifying code; debugging and relocation become muchì
  436. more difficult, and you must insure that the variable NEVER has anì
  437. unexpected value.  Also, in microprocessors with instruction caches (fastì
  438. memory containing copies of the contents of regular memory), there can beì
  439. problems if the cache data are not updated.
  440.  
  441.    I put a LABEL at each instruction with an immediate variable, then useì
  442. LABEL+1 for all references to it.  This serves as a reminder that somethingì
  443. odd is going on.  Be sure to document what you're doing, or you'll driveì
  444. some poor soul (probably yourself) batty.
  445.  
  446.  
  447. Exclusive OR
  448.  
  449.    The XOR operator is a powerful tool for manipulating data.  Sinceì
  450. anything XOR'd with itself is 0, use XOR A instead of LD A,0.  To toggle aì
  451. variable between two values, XOR it with the difference between the twoì
  452. values.  Our last example can be performed much more efficiently by:
  453.  
  454. toggle: ld     (stack+1),sp        ; save old stack pointer 
  455.         ld     sp,mystack          ; setup my stack
  456. flag:   ld     a,'Y'               ; get Y/N flag (byte 2=var)
  457.         xor    'Y'-'N'             ; toggle "Y" <-> "N"
  458.         ld     (flag+1),a          ; save new state
  459. stack:  ld     sp,0                ; restore stack (byte 2,3=var)
  460.         retè
  461.    XOR eliminated the jump, for a 2:1 improvement in size and speed.  Thisì
  462. illustrates a generally useful rule.  Almost any permutation can beì
  463. performed faster, without jumps, by XOR and the other math and logicalì
  464. operators.  Consider the following routine to convert the ASCII character inì
  465. A to uppercase: it's both shorter and faster than the traditional methodì
  466. using jumps.
  467.  
  468. convert: ld    b,a            ; save a copy of the char in B
  469.          sub   'a'            ; if char is lowercase (a thru z),
  470.          cp    'z'-'a'+1      ;   then carry=1, else carry=0
  471.          sbc   a,a            ;   fill A with carry
  472.          and   'a'-'A'        ;   difference between upper/lower
  473.          xor   b              ;   convert to uppercase
  474.  
  475. Data Compaction
  476.  
  477.    Programs frequently include large blocks of text, data tables, and otherì
  478. non-program data.  Careful organization of such information can produceì
  479. large savings in memory and speed of execution.
  480.  
  481.    ASCII is a 7-bit code.  The 8th bit of each byte is either unused or justì
  482. marks the end of a string.  You can bit-pack 8 characters into 7 bytes withì
  483. a suitable routine.  If upper case alone is sufficient, 6 bits are enough.ì
  484. For dedicated applications, don't overlook older but more memory-efficientì
  485. codes like Baudot (5 bits), EBCD (4 bits), or even International Morse (2-10ì
  486. bits, with frequent characters the shortest).
  487.  
  488.    If your text is destined for a CRT or printer, it may be heavy on controlì
  489. characters and ESC sequences.  I've found the following algorithm useful.ì
  490. Bytes whose msb=0 are normal ASCII characters: output as-is.  Printableì
  491. characters whose msb=1 are preceeded by ESC, so "A"+80h=C1h sends "ESC A".ì
  492. Control codes whose msb=1 are a "repeat" prefix to output the next byteì
  493. between 2 and 32 times.  For example, linefeed+80h=8Ah repeats the nextì
  494. character 11 times.  The value 80h, which otherwise would be "repeat once",ì
  495. is reserved as the marker for the end of the string. 
  496.  
  497.    Programs can be compacted, too.  One technique is to write your programì
  498. in an intermediate language (IL) better suited to the task at hand.  Itì
  499. might be a high-level language, the instruction set of another CPU, or aì
  500. unique creation specifically for the job at hand.  The rest of your programì
  501. is then an interpreter to execute this language.  Tom Pittman's Tiny BASICì
  502. is an excellent example of this technique.  His intermediate languageì
  503. implemented BASIC in just 384 bytes; the IL interpreter in turn took aboutì
  504. 2K.
  505.  
  506. Another approach is threaded code, made popular by the FORTH language.  Aì
  507. tight, well-structured program will probably use lots of CALLs.  At theì
  508. highest levels, the code may in fact be nothing but long sequences of CALLs:
  509.  
  510. main:   call  getname
  511.         call  openfileè        call  readfile
  512.         call  expandtabs
  513.         call  writefile
  514.         call  closefile
  515.         ret
  516.      
  517.    Every 3rd byte is a CALL; large programs will have 1000s of them.  Soì
  518. let's eliminate the CALL opcodes, making our program just a list ofì
  519. addresses:
  520.  
  521. main:   ld    (stack),sp ; save stack pointer
  522.         ld    sp,first   ; point it to first address in the list
  523.         ret              ; and go execute it
  524. first:  dw    openfile
  525.         dw    readfile
  526.         dw    expandtabs
  527.         dw    writefile
  528.         dw    closefile
  529.         dw    return     ; end of list
  530.  
  531. return: ld    sp,(stack) ; restore stack pointer
  532.         ret              ; and return (to MAIN's caller)
  533.  
  534. The stack pointer is pointed to the address of the first subroutine in theì
  535. list to execute.  RET then loads this address into the program counter andì
  536. advances the stack pointer to the next address.  Since each subroutine alsoì
  537. ends with a RET, it automatically jumps directly to the next routine in theì
  538. list to be executed.  This is called directly threaded code. 
  539.  
  540.    RETURN is always the last subroutine in a list.  It restores the stackì
  541. pointer and returns to the caller of MAIN. 
  542.  
  543.    Directly threaded code can cut program size up to 30%, while actuallyì
  544. increasing execution speed.  However, it has some rather drasticì
  545. limitations.  During execution of the machine-code subroutines in theì
  546. address list, the Z80's one and only stack pointer is tied up as an addressì
  547. pointer.  That means the stack can't be used; no interrupts, calls, pushes,ì
  548. or pops are allowed without first switching to a local stack.
  549.  
  550.    The solution to this is called indirectly threaded code, made famous (orì
  551. infamous) by the FORTH language.  Rather than have each subroutine directlyì
  552. chain into the next, they are linked by a tiny interpreter, called NEXT:
  553.  
  554. main:   call  next
  555.         dw    openfile
  556.         dw    readfile
  557.         dw    expandtabs
  558.         dw    writefile
  559.         dw    closefile
  560.         dw    return     ; end of list
  561.  
  562. next:   pop   ix         ; make IX our next-subroutine pointerènext1:  ld    hl,next1   ; push address so RET comes back here
  563.         push  hl
  564.         ld    l,(ix+0)   ; get address to "call"
  565.         inc   ix         ;    low byte
  566.         ld    h,(ix+0)   ;    high byte
  567.         inc   ix         ; point IX to addr for next time
  568.         jp    (hl)       ; call address
  569.  
  570. return: pop   hl         ; end of list; discard NEXT addr
  571.         ret              ; and return to MAIN's caller
  572.  
  573.    Now IX is our pointer into the address list; it points to the nextì
  574. subroutine to be executed.  Subroutines can use the stack normally within,ì
  575. but must preserve IX and can't pass parameters in HL.  When they exit viaì
  576. RET, it returns them to NEXT1.
  577.  
  578.    Though the example executes the address list as straight-line code,ì
  579. subroutines can be written to perform jumps and calls via IX as well.  NEXTì
  580. can provide special handling for commonly-used routines as well; words withì
  581. the high byte=0 could jump IX by a relative offset if A=0.  If there areì
  582. less than 256 subroutines, each address can be replaced by a single byte,ì
  583. which NEXT converts into an address via a lookup table. 
  584.  
  585.    Indirectly threaded code can reduce size up to 2:1 in return for aì
  586. similar loss in execution speed.  The decrease in program size is oftenì
  587. remarkable. I learned this in 1975 designing a sound-level dosimeter.  Thisì
  588. cigarette-pack sized gadget rode around in a shirt pocket all day, loggingì
  589. the noise a person was exposed to.  It then did various statisticalì
  590. computations to report the high, low, mean, and RMS noise levels versusì
  591. time.
  592.  
  593.    In those dark ages, a BIG memory chip was 256x4.  Cost, power, and sizeì
  594. forced us into an RCA 1802 CMOS microprocessor, with just 512 bytes ofì
  595. program memory (bytes, not K!).  Try as we might, we couldn't do it.  Inì
  596. desperation, we tried Charlie Moore's FORTH.  Incredibly, it bettered evenì
  597. our heavily optimized code by 30%! 
  598.  
  599.    Of course, very little of FORTH itself wound up in the final product; itì
  600. just showed us the way.  Once you know HOW it's done, you can apply the sameì
  601. techniques to any assembly-language program without becoming a born-againì
  602. FORTH zealot.
  603.  
  604.  
  605. Shortcuts
  606.  
  607.    Here are some "quickies" that didn't fit in elsewhere.  Keep in mind whatì
  608. is actually in all the registers as you program.  Do you really need toì
  609. clear carry, or is it already cleared as the result of a previous operation? ì
  610. Before you load a register, are you sure it's necessary?  Perhaps it'sì
  611. already there, or sitting in another register.
  612.  
  613.    Many routines return "leftovers" that can be very useful, such as HL, DE,ìèand BC=0 after a block move.  Perhaps an INC or DEC will produce the valueì
  614. you want.  Variables can be grouped so you needn't reload the entire addressì
  615. for each.  If the high byte of a register is correct, just load the lowerì
  616. half.
  617.  
  618.    Keep an index register pointed to your frequently-used variables.  Thisì
  619. makes them easier to access (up to 256 bytes) and opens the door to memory-ì
  620. (rather than register-) oriented manipulations.  The indexed instructionsì
  621. are slower and less memory-efficient, but the versatility sometimes makes upì
  622. for it (store an immediate byte to memory, load/save to memory fromì
  623. registers other than A, etc.).
  624.  
  625.    The Z80's bit test/set/reset instructions add considerable versatility ifì
  626. you define your flags as bits rather than bytes.  Bit flags can be accessedì
  627. in any register, or even directly in memory, without loading them into aì
  628. register.
  629.  
  630.    If the last two instructions of a subroutine are CALL FOO and RET, youì
  631. could just as well end with JP FOO and let FOO do the return for you.  Ifì
  632. the entry point of FOO is at the top, even the JP is unnecessary; you canì
  633. locate FOO immediately after and "fall in" to it.
  634.  
  635.    If you have a large number of jumps to a particular label (like the startì
  636. of your MAIN program), it may be more efficient to push the address of MAINì
  637. onto the stack at the top of the routine.  Each JP MAIN can then be replacedì
  638. by a 1-byte RET.
  639.  
  640.    SKIP instructions are a short, fast way to jump a fixed distance.  Theì
  641. Z80 has no skips, but you can simulate a 1- or 2-byte skip with a 2- orì
  642. 3-byte do-nothing instruction: JR or JP on a condition that is never true,ì
  643. for instance.  If the flags aren't in a known state, load to an unusedì
  644. register.  For example:
  645.  
  646. clear1:   ld   a,1       ; clear 1 byte
  647.           db   21h       ;   skip next two bytes (21h = ld hl,nn)
  648. clear80:  ld   a,80      ; clear 80 bytes (and "nn" for ld hl,nn)
  649.           db   26h       ;   skip next byte (26h = ld h,n)
  650. clear256: xor  a         ; clear 256 bytes (and "n" for ld h,n)
  651.  
  652. clear:    ld   b,a       ; clear #bytes in A to zero
  653.           ld   hl,buffer ;   beginning at buffer
  654. loop:     ld   (hl),0
  655.           inc  hl
  656.           djnz loop
  657.           ret
  658.  
  659.    The stack pointer is the Z80's only auto-increment/decrement register. ì
  660. This makes it uniquely suitable for fast block operations.  For instance,ì
  661. the fastest way to clear a block of RAM is to make it the stack and push theì
  662. desired data.  At 11 T-states per 2 bytes, it is 3 times faster than two LDD ì
  663. instructions.  Remember to disable interrupts or to allow for them; if anì
  664. interrupt routine pushes onto the stack while you are using it for thisìèspecial purpose, the results may not be what you intended.  
  665.  
  666.    That is all for this time.  Next time we will continue the discussionì
  667. with a look at the interplay between software and hardware.
  668.  
  669. [This article was originally published in issue 39 of The Computer Journal,
  670. P.O. Box 12, South Plainfield, NJ 07080-0012 and is reproduced with the
  671. permission of the author and the publisher. Further reproduction for non-
  672. commercial purposes is authorized. This copyright notice must be retained.
  673. (c) Copyright 1989, 1991 Socrates Press and respective authors]
  674.