home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #18 / NN_1992_18.iso / spool / comp / theory / cellaut / 343 < prev    next >
Encoding:
Text File  |  1992-08-19  |  16.7 KB  |  352 lines

  1. Newsgroups: comp.theory.cell-automata
  2. Path: sparky!uunet!charon.amdahl.com!pacbell.com!mips!sdd.hp.com!elroy.jpl.nasa.gov!decwrl!csus.edu!netcom.com!pdh
  3. From: pdh@netcom.com (Phil Howard )
  4. Subject: Using lookup tables for faster Life Game calculations
  5. Message-ID: <a_#n=la.pdh@netcom.com>
  6. Date: Thu, 20 Aug 92 02:30:29 GMT
  7. Organization: Netcom - Online Communication Services  (408 241-9760 guest) 
  8. Keywords: 8086 real mode lookup table life game
  9. Lines: 341
  10.  
  11. /************************************************************\
  12. |Copyright (C) 1992, by Philip D. Howard, all rights reserved|
  13. |Permission is hereby granted to Usenet, and the underlying  |
  14. |networks making up Usenet, to distribute this article.      |
  15. \************************************************************/
  16.  
  17.  
  18. Lookup table logic is already a well known method of optimizing program
  19. code in many situations.  The Game of Life is one of those cases where
  20. the method can definitely speed up the calculation process.  However
  21. such use of lookup tables for the Game of Life is not particularly
  22. trivial, so I will be summarizing the explorations in this area I have
  23. made, which has resulted in at least one fast working executable program.
  24.  
  25.  
  26.  
  27. BACKGROUND
  28.  
  29. For those unfamiliar, the Game of Life is a cellular automata simulation
  30. game.  Others who participate in the comp.theory.cell-automata Usenet
  31. newgroup could give better citations than I can.  Unfortunately I cannot
  32. find an FAQ (Frequently Asked Questions, with answers) posting.
  33.  
  34. The original Game of Life, published by Conway in an article in Scientific
  35. American involved a rectangular array of positions in which a cell may or
  36. may not be alive.  Time occurred in generations in which the cells could
  37. be born or die.  The original rules specified that a live cell would die
  38. (of loneliness) if it had fewer than 2 neighbors, or die (of overcrowding
  39. or lack of food) if it had more than 3 neighbors.  So in order for a cell
  40. to continue to live, it must have either 2 or 3 neighbors.  A new cell
  41. could be born into an empty position (no live cell there currently) if
  42. that position had exactly 3 neighboring live cells.  Both the orthogonal
  43. and diagonal positions were counted, but they had to be the adjacent ones.
  44.  
  45. Variations have also since been experimented with both on the same board
  46. as above, or in different arrangements (hexagonal, 3d, etc).  In this
  47. article I will be limiting discussion to just the original rectangular
  48. board (array of cell positions).
  49.  
  50.  
  51.  
  52. EARLY EFFORTS
  53.  
  54. When I first read about the Game of Life, I acquired a program for my
  55. Apple II computer to run the simulation.  It was very obvious that
  56. figuring the generations by hand was no fun at all.  The computer did
  57. the work for me just fine, until I wanted to explore lots of new cell
  58. configurations to see what they did.  I also wanted to explore large
  59. configurations, but the program only did 40x48 or so.  The program I
  60. got was rather slow (about 1 generation per two seconds).  I then tried
  61. to write one of my own and it ended up being even slower.  This was in
  62. BASIC so it was obvious I needed machine language.
  63.  
  64. I also wanted to use the Hi-Res display mode of the Apple II for a larger
  65. configuration.  The Apple II stored 7 pixels per byte for a width of 280
  66. pixels and a height of 192 pixels.  Obviously this meant even more work
  67. for the computer, so speed was essential.  The first draft of my program
  68. had a lot of shifting going on, cell counting, and some code to check
  69. the numbers against the rules to decide whether a cell should be in the
  70. given position in the next generation.
  71.  
  72. While seeking to find ways to reduce the amount of shifting of bits that
  73. was taking place, I realized that the neighbor bits in each row were
  74. already in a unique group, and maybe I could capitalize on that in some
  75. way.  So the first approach I took was to use the 3 cells in either the
  76. row above or the row below, and use them as a lookup to a table that
  77. gave me the count.  I didn't need to mask off the 3 bits because I made
  78. the table repeat the counts to fill out 256 bytes in a way that treated
  79. the other 5 bits as irrelevant.  At the expense of some memory I saved
  80. several CPU cycles.
  81.  
  82. Later on I tried a different approach of collecting all 8 neighbor bits
  83. into an index, which directly got the data without the counting and the
  84. test of the count.  One further test of the center cell made a simple
  85. branch.  In order to make things as fast as possible, I designed 7
  86. different types of machine code for each of the 7 different pixel
  87. positions in a byte.  The resultant program ran at about the same speed
  88. as the first program I had, but now was doing 280x192 instead of 40x48.
  89. By limiting the size I got a significant speedup.
  90.  
  91. I've since sold my Apple II, and gave all my old Apple II disks to a
  92. friend who probably erased them to put his games on.  I no longer have
  93. any of that code.
  94.  
  95.  
  96.  
  97. RECENT ADVANCES
  98.  
  99. More recently I have considered again the writing of a program to run
  100. the Game of Life, but this time on a PC clone architecture.  The EGA
  101. and VGA displays have a video mode (number 13, hexadecimal 0D) in which
  102. there are 320x200 pixels, organized so that each plane (4 planes total)
  103. contains 8 pixels per byte.  I could use just one plane to store that
  104. data as well as retrieve it.
  105.  
  106. The original Apple II was limited to 48K (subsequent models have pushed
  107. that figure up) but the PC architecture in 8086 real mode had a memory
  108. capacity of 640K (not utilizing the memory locations reserved for things
  109. like video).  This allowed for an even larger lookup table.
  110.  
  111. The original method I examined involved calculating 4 cells in parallel.
  112. For each byte, 2 sections of code would be used, one for the higher 4 bits
  113. and one for the lower 4 bits.  I chose to use a linear arrangement of
  114. 4 bits to make the calculation of a full byte of cells easier to do.
  115.  
  116. In these graphs, the cells being calculated are h, i, j, and k:
  117.  
  118.         higher 4 cells:                 lower 4 cells:
  119.  
  120.         .......a bcdef...               ...abcde f.......
  121.         .......g hijkl...               ...ghijk l.......
  122.         .......m nopqr...               ...mnopq r.......
  123.  
  124. The dots represent bit positions that do not participate, and the space
  125. is the boundary between bytes.  For each 4 cells being calculated, it
  126. was necessary to load 6 bytes of data, and perform some shifting and
  127. masking.  Since this is a total of 18 bits and the target machine had
  128. a limit of 64K for a single array, I decided to split things up and
  129. utilize the segmented memory access of the 8086.  The lookup table was
  130. allocated as 32 pieces of 8K bytes.  A selected 5 bits was used to
  131. index into a table of segment numbers, and the other 13 bits was used
  132. as an offset.
  133.  
  134. For the upper 4 bits, the bits were shifted around so that:
  135.     segment index = ........ ...hijkl
  136.     offset        = ...nopqr bcdefagm
  137.  
  138. For the lower 4 bits, the bits were shifted around so that:
  139.     segment index = ........ ...ghijk
  140.     offset value  = ...mnopq rlfabcde
  141.  
  142. Since each lookup table entry had pre-calculated bits in the proper
  143. position, it would have been a simple matter when generating the table
  144. to do the upper 4 bits one way, and the lower 4 bits a different way.
  145. The reason for the different arrangement was to minimize the total
  146. amount of shifting being done.
  147.  
  148. The C code to calculate the upper 4 bits is like:
  149.  
  150.         b = segref( uchar,
  151.                 seglist[ ( c2 & 248 ) >> 3 ],           // segindex HIJKl
  152.                     ( (
  153.                         ( ( u1 & 1 ) << 1 )             // ..... ......a.
  154.                         | ( c1 & 1 )                    // ..... .......g
  155.                     ) << 1 )                            // ..... .....ag.
  156.                     |   ( d1 & 1 )                      // ..... .......m
  157.                     |   ( u2 & 248 )                    // ..... bcdef...
  158.                     | ( ( d2 & 248 ) << 5 )             // nopqr ........
  159.         ) & 0xf0;
  160.  
  161. The C code to calculate the lower 4 bits is like:
  162.  
  163.         b |= segref( uchar,
  164.                 seglist[ c1 & 31 ],                     // segindex gHIJK
  165.                     ( (
  166.                         ( ( u2 & 128 ) >> 1 )           // ..... .f......
  167.                         | ( c2 & 128 )                  // ..... l.......
  168.                     ) >> 1 )                            // ..... .lf.....
  169.                     |   ( d2 & 128 )                    // ..... r.......
  170.                     |   ( u1 & 31 )                     // ..... ...abcde
  171.                     | ( ( d1 & 31 ) << 8 )              // mnopq ........
  172.         ) & 0x0f;
  173.         segref( uchar , arg_next , tp++ ) = b;
  174.  
  175. The array "seglist" contained the 32 segment values (paragraph number).
  176.  
  177. The C macro "segref" is a macro I wrote to simply accessing memory when
  178. I am specifying the segment and offset separately.  It results in an
  179. lvalue in the C language, which can be used to fetch or store data.
  180. Argument 1 is the data type, argument 2 is the segment, and argument
  181. 3 is the offset.
  182.  
  183. The variables u1, c1, d1, u2, c2, and d2 were pre-loaded with the data
  184. from the old generation.  I was hoping the C compiler would handle these
  185. in registers, but unfortunately the 8086 has too few registers, and those
  186. it does have are too limited (for instance you cannot use AX, CX, or DX
  187. as base registers).  So it allocated memory even for these.  This would
  188. be a loss primarily on non-cache machines.
  189.  
  190. The speed results were quite good (at full 320x200):
  191.  
  192. Gateway 2000 386/25 (no cache): 6.75 generations per second
  193. IBM PS/2-70:            approx  7.5  generations per second
  194. Gateway 2000 486/33C:          17.5  generations per second
  195.  
  196.  
  197.  
  198. GOING EVEN FASTER
  199.  
  200. I studied the machine code the compiler generated and was still convinced
  201. it should be possible to go even faster.  There were a lot of shift
  202. operations being done, and neither I nor the compiler could find any way
  203. to optimize any of it (I tried the maximum optimization of the MicroSoft C
  204. compiler).
  205.  
  206. So I needed something else if I was going to get any more speed.  One of
  207. the things I noticed was that only a 1 column slice was being taken of the
  208. adjacent byte.  If I shifted the working position over, I could have ONE
  209. of the two "phases" of calculation being performed on only 3 bytes instead
  210. of 6 bytes.  The input would also be better aligned resulting in less
  211. shifting of data.  The collection of the new cells would be more complex
  212. but no new shifting was needed to do so since the lookup table would
  213. hold cells in the ultimate position anyway.
  214.  
  215. As an added bonus, I discovered that some of the shifting was 4 bits,
  216. which suggested a further possible savings by taking advantage of the
  217. inherint 4 bit shift of the address calculation in the 8086 real mode
  218. memory segmentation.  So a new construction of the 18 bits to form an
  219. address in the lookup table looks like this.  In order to show the
  220. effect of the 4 bit shift in the segment value, the graphic will show
  221. the inherint shift:
  222.  
  223.         first 4 cells:                  second 4 cells:
  224.  
  225.         ......ab cdef....               ..abcdef
  226.         ......gh ijkl....               ..ghijkl
  227.         ......mn opqr....               ..mnopqr
  228.  
  229. For the first 4 bits, the bits were shifted around so that:
  230.     segment = ..gh .... cdef ....
  231.     offset  =      ijkl .... opqr abmn
  232.     index   =   gh ijkl cdef opqr abmn
  233.  
  234. For the second 4 bits, the bits were shifted around so that:
  235.     segment = ..gh ijkl .... ....
  236.     offset  =      .... abcd efmn opqr
  237.     index   =   gh ijkl abcd efmn opqr
  238.  
  239. Since the segment portion of the index was being constructed entirely
  240. in position, it was necessary to have the lookup table entirely contiguous
  241. in memory.  The base segment value of the table would be added to the
  242. calculated segment value to be used as a portion of the address of the
  243. ultimate memory location in the lookup table.
  244.  
  245. I am currently coding this method in assembly language instead of C,
  246. and am also including the ability to have any board width, not just
  247. multiples of 8 which would have been easier to do.  Therefore the
  248. edge calculations are more complex and separate code is dedicated.
  249. This version will also allow choosing to have wrap around between
  250. top and bottom, or between left and right, or both.
  251.  
  252.  
  253.  
  254. REDUCING THE MEMORY
  255.  
  256. Both of the methods described above for the PC/8086 use an 18 bit index
  257. and so requires a lookup table of 256K bytes.  Several people have reported
  258. that the first beta-test version I released would not run on their machine
  259. because it was out of memory (some additional memory was require for the
  260. code, I/O buffers, a copy of the active board, and an active shape pattern.
  261. It would certainly be nice to find a way to reduce the amount of memory
  262. required.
  263.  
  264. One method that was suggested was to organize the 4 cells in a square
  265. instead of a line.  This would result in only 16 bits instead of a 18
  266. bits for the index.  The memory used would be 64K.  The configurations
  267. would be like:
  268.  
  269. phase 0:                phase 1:        phase 2:        phase 3:
  270.  
  271. ......ab cd......       abcd....        ..abcd..        ....abcd
  272. ......ef gh......       efgh....        ..efgh..        ....efgh
  273. ......ij kl......       ijkl....        ..ijkl..        ....ijkl
  274. ......mn op......       mnop....        ..mnop..        ....mnop
  275.  
  276. In each case, the cells f, g, j, and k are being calculated.
  277.  
  278. The number of phases (different sets of code) needed is 4 since the
  279. width would be 2 instead of 4, and that results in 4 different positions
  280. within a byte.  In addition, each use of the lookup table will have
  281. to be different for the upper 2 new cells than for the lower 2 new cells.
  282. Since the cells that contribute are different, the need to be pulled
  283. in from different places in the lookup table.  This could either be a
  284. different bit position (but these are already used up for the 4 phases)
  285. or a different byte (which doubles the table size from 64K to 128K).
  286.  
  287. At the present time I have chosen not to explore the 2x2 method any further.
  288. It does have potential, but it also looks like it will be more complex.
  289.  
  290.  
  291.  
  292. Another possible method that I do plan to explore involves calculating
  293. fewer cells at a time, trading some speed for even more memory savings.
  294. The organization would be:
  295.  
  296. phase 0:                phase 1:        phase 2:
  297.  
  298. ......ab cd......       abcde...        ...abcde
  299. ......ef gh......       fghij...        ...fghij
  300. ......ij kl......       klmno...        ...klmno
  301.  
  302. The cells being calculated in phase 0 are f and g.  The cells being
  303. calculated in phases 1 and 2 are g, h, and i.
  304.  
  305. The number of bits to form an index is only 15 (12 in phase 0, but this
  306. gives us the freedom to place them anywhere in the lower 15 bit positions)
  307. so the table size needed is only 32K bytes.
  308.  
  309. I plan to try to implement this method in C without using any segment and
  310. offset tricks, although pre-processor selected code may be used to force
  311. based addresses to be sure that the index is used as an offset directly
  312. instead of being added to a far address in the 8086 architecture.  Other
  313. architectures would be compiled using ordinary indexing.
  314.  
  315.  
  316.  
  317. SUMMARY
  318.  
  319. The lookup table, especially the modified form where multiple cells are
  320. effectively calculated in parallel does offer a speedup advantage at the
  321. cost of storing the table itself.  Care must be taken in limited machine
  322. architectures when the tables are large, but some tricky programming may
  323. also allow one to select some advantages in these cases.
  324.  
  325. This method of using a lookup table allows for using any kind of rules
  326. for a rectangular board where cells are affected by their immediate eight
  327. neighbors.  I have not yet explored any of these variations, not have I
  328. constructed code that would flexibly build the lookup table from a rule
  329. set.  I am looking for ways to specify a rule set before doing this.
  330.  
  331. Very large memory spaces (particularly if an equivalent amount of real
  332. memory is available to keep the lookup table resident in virtual memory
  333. systems) may offer significant further speedups.  I look forward to the
  334. day I have a machine with 2 gigabytes of real memory, of which 1 gigabyte
  335. can be used to hold the lookup table needed for a 30 bit index which
  336. would let me calculate a full 8-bit byte all at once.
  337.  
  338. If you would like a uuencoded or xxencoded copy of the PCLIFE program
  339. I wrote using the first 4-bit method I described, send me e-mail to
  340. the address:  pdh@netcom.com  and ask for a copy.  The ZIP file will
  341. include a few sample boards, selected via the 12 function keys.  The
  342. user interface is this version is very rough and incomplete; some
  343. commands are not implemented.  Suggestions for a full release version
  344. are invited.  If you already have a user interface but would like to
  345. add my generation code, please contact me at the same e-mail address.
  346. I will send uuencoded unless you specify xxencoded.
  347. -- 
  348. /***********************************************************************\
  349. | Phil Howard  ---  KA9WGN  ---  pdh@netcom.com   |   "The problem with |
  350. | depending on government is that you cannot depend on it" - Tony Brown |
  351. \***********************************************************************/
  352.