Initial conversion to markdown
This commit is contained in:
parent
a96a114403
commit
5e10794a24
362 changed files with 67353 additions and 0 deletions
123
18-05.md
Normal file
123
18-05.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
------------------------ --------------------------------- --------------------
|
||||
[Previous](18-04.html) [Table of Contents](index.html) [Next](19-01.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
“The basic idea is to maintain a ‘change list.’ This is an array of
|
||||
pointers into the cell array. Each change list element points to a word
|
||||
which changes in the next generation. This way we don’t have to waste
|
||||
time scanning every cell since most of them do not change. Two passes
|
||||
are made through the change list. The first pass updates the cell
|
||||
display on the screen, sets the life/death status of each cell for this
|
||||
new generation, and updates the neighbor counts for the adjacent cells.
|
||||
There are some efficiencies gained by using cell triplets rather than
|
||||
individual cells since we usually don’t need to set all eight neighbors.
|
||||
[Again, the neighbor counts for cells in the same word are implied by
|
||||
the states of those cells.] The second pass sets the next-generation
|
||||
states for the cells and their neighbors, and in the process builds the
|
||||
change list for the next generation.
|
||||
|
||||
“Processing each word is a little complex but very fast. A 64K block of
|
||||
code exists with routines on each 256-byte boundary. Generally speaking,
|
||||
the entry point corresponds to the high byte of the cell word. This byte
|
||||
contains the life/death values and a bit to indicate if this is an edge
|
||||
condition. During the first pass we take the cell triplet word, AND it
|
||||
with 0XFE00, and jump to that address. During the second pass we take
|
||||
the cell triplet word, AND it with 0xFE00, OR it with 0x0100, and jump
|
||||
to that address. [Therefore, there are 128 possible jump targets on the
|
||||
first pass, and 128 more on the second, all on 256-byte boundaries and
|
||||
all keyed off the high 7 bits of the cell triplet state; because bit 8
|
||||
of the jump index is 0 on the first pass and 1 on the second, there is
|
||||
no conflict. The lower bit isn’t needed for other purposes because only
|
||||
the edge flag bit and the six life/death state bits matter for jumping
|
||||
into David’s state machine. The other nine bits, the bits used for the
|
||||
neighbor counts, are used only in the next step.]
|
||||
|
||||
“Determining which changes must be made to a cell triplet is easy and
|
||||
surprisingly quick. There’s no counting! Instead, I use a 64K lookup
|
||||
table indexed by the cell triplet itself. The value of the lookup table
|
||||
entry is equal to what the high byte should be in the next generation.
|
||||
If this value is equal to the current high byte, then no changes are
|
||||
necessary to the cell. Otherwise it is placed in the change list. Look
|
||||
at the code in the **Test()** and **Fix()** functions to see how this is
|
||||
done.” [This step is as important as it is obscure. David has a 64K
|
||||
table organized so that if you use a word describing a cell triplet as a
|
||||
lookup index, the byte you will read will be the state of the high byte
|
||||
for the next generation. In other words, David’s table is constructed so
|
||||
that the edge flag bit, the life/death states, and the three neighbor
|
||||
count fields form an index to a byte describing the next generation
|
||||
state for that triplet. In practice, only the next generation field of
|
||||
the cell changes. Then, if another change to a nearby cell tries to
|
||||
nudge that cell into changing again, David’s code sees that the desired
|
||||
state is already set, and does not add that cell to the change list
|
||||
again.]
|
||||
|
||||
Segment usage in David’s assembly code is summarized in Listing 18.6.
|
||||
|
||||
**LISTING 18.6 QLIFE Assembly Segment Usage**
|
||||
|
||||
CS : 64K code (table of routines on 256 byte boundaries)
|
||||
DS : DGROUP (1st pass) / 64K cell life/death classification table (second pass)
|
||||
ES : Change list
|
||||
SS : DGROUP; the life cell grid and row/column table
|
||||
FS : Video segment
|
||||
GS : Unused
|
||||
|
||||
#### A Layperson’s Overview of QLIFE {#Heading6}
|
||||
|
||||
Most likely, you’re scratching your head right now in bemusement. I
|
||||
don’t blame you; I felt the same way myself at first. It’s actually
|
||||
pretty simple, though, once you have the hang of it. Basically, David
|
||||
runs down the change list, visiting every cell that’s due to change in
|
||||
this generation, setting it to the new state, drawing it in the new
|
||||
state, and adjusting the counts of all its neighbors. David has a
|
||||
separate assembly routine for every possible change of state for a cell
|
||||
triplet, and he jumps to the proper routine by taking the cell triplet
|
||||
word, masking off the lower 9 bits, and jumping to the address where the
|
||||
appropriate code to perform that particular change of state resides. He
|
||||
does this for every entry in the change list. When this is completed,
|
||||
the current generation has been drawn and updated.
|
||||
|
||||
Now David runs down the change list again to generate the change list
|
||||
for the next generation. In this case, for every changed cell triplet,
|
||||
David looks at that triplet and all affected neighbors to see which will
|
||||
change in the next generation. He tests for this condition by using each
|
||||
potentially changed cell triplet word as an index into the
|
||||
aforementioned lookup table of new states. If the current state matches
|
||||
the appropriate state for the next generation, then there’s nothing to
|
||||
do and the cell is not added to the change list. If the states don’t
|
||||
match, then the cell is added to the change list, and the appropriate
|
||||
state for the next generation is set in the cell triplet. David checks
|
||||
the minimum possible number of cells for change by branching to code
|
||||
that checks only the relevant cells around each cell triplet in the
|
||||
current change list; that branching is accomplished by taking the cell
|
||||
triplet word, masking off the lower 9 bits, setting bit 8 to a 1-bit,
|
||||
and branching to the routine at that address. As with everything in this
|
||||
amazing program, this represents the least possible work to accomplish
|
||||
the desired result—just three instructions:
|
||||
|
||||
mov dh,[bp+1]
|
||||
or dh,1
|
||||
jmp dx
|
||||
|
||||
These suffice to select the proper, minimum-work code to process the
|
||||
next cell triplet that has changed, and all potentially affected
|
||||
neighbors. For all the size of David’s code, it has an astonishing
|
||||
economy of effort, as execution glides through the change list without a
|
||||
wasted instruction.
|
||||
|
||||
Alas, I don’t have the room to discuss Peter Klerings’ equally
|
||||
remarkable Life implementation here. I’ll close this chapter with a
|
||||
quote from Terje Mathisen, one of the finest optimizers it has ever been
|
||||
my pleasure to meet, who, after looking over David’s and Peter’s
|
||||
entries, said, “This has been an eye-opening experience for me. I
|
||||
honestly thought I had the fastest possible approach.” TANSTATFC.
|
||||
|
||||
There Ain’t No Such Thing As the Fastest Code.
|
||||
|
||||
------------------------ --------------------------------- --------------------
|
||||
[Previous](18-04.html) [Table of Contents](index.html) [Next](19-01.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
* * * * *
|
||||
|
||||
Graphics Programming Black Book © 2001 Michael Abrash
|
||||
Loading…
Reference in a new issue