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