164 lines
8.3 KiB
Markdown
164 lines
8.3 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[Previous](17-04.html) [Table of Contents](index.html) [Next](17-06.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
In Listing 17.3, note the padded cellmap edges, and the alteration of
|
||
the member functions to compensate for the padding. Also note that the
|
||
width now has to be a multiple of eight, to facilitate the process of
|
||
copying the edges to the opposite padding bytes. We have decreased the
|
||
generality of our Game of Life implementation in exchange for better
|
||
performance. That’s a very common trade-off, as common as trading memory
|
||
for performance. As a rule, the more general a program is, the slower it
|
||
is. A corollary is that often (not always, but often), the more heavily
|
||
optimized a program is, the more complex and the more difficult to
|
||
implement it is. You can often improve performance a good deal by
|
||
implementing only the level of generality you need, but at the same time
|
||
decreased generality makes it more difficult to change or port the
|
||
program at some later date. A Game of Life implementation, such as
|
||
Listing 17.1, that’s built on **set\_cell()**, **clear\_cell()**, and
|
||
**get\_cell()** is completely general; you can change the cell storage
|
||
format simply by changing the constructor and those three functions.
|
||
Listing 17.3 is harder to change because **count\_neighbors()** would
|
||
also have to be altered, and it’s more complex than any of the other
|
||
functions.
|
||
|
||
So, in Listing 17.3, we’ve gotten under the hood and changed the cellmap
|
||
format a little, and gotten impressive results. But now
|
||
**count\_neighbors()** is hard-wired for optimized counting, and it’s
|
||
still taking up more than half the time. Maybe now it’s time to go to
|
||
assembly?
|
||
|
||
Not hardly.
|
||
|
||
### Heavy-Duty C++ Optimization {#Heading7}
|
||
|
||
Before we get to assembly, we still have to perform C++ optimization,
|
||
then see if we can find an alternative approach that better fits the
|
||
application. It would actually have made much more sense if we had
|
||
looked for a new approach as our first optimization step, but I decided
|
||
it would be better to cover straightforward C++ optimizations at this
|
||
point, and the mind-bending stuff a little later. Right now, let’s look
|
||
at some C++ optimizations; Listing 17.4 is a C++-optimized version of
|
||
Listing 17.3.
|
||
|
||
**LISTING 17.4 L17-4.CPP**
|
||
|
||
/* next_generation(), implemented using fast, all-in-one hard-wired
|
||
neighbor count/update/draw function. Otherwise, the same as
|
||
Listing 17.3. */
|
||
|
||
/* Calculates the next generation of current_map and stores it in
|
||
next_map. */
|
||
void cellmap::next_generation(cellmap& next_map)
|
||
{
|
||
unsigned int x, y, neighbor_count;
|
||
unsigned int width_in_bytesX2 = width_in_bytes << 1;
|
||
unsigned char *cell_ptr, *current_cell_ptr, mask, current_mask;
|
||
unsigned char *base_cell_ptr, *row_cell_ptr, base_mask;
|
||
unsigned char *dest_cell_ptr = next_map.cells;
|
||
|
||
// Process all cells in the current cellmap
|
||
row_cell_ptr = cells; // point to upper left neighbor of
|
||
// first cell in cell map
|
||
for (y=0; y<height; y++) { // repeat for each row of cells
|
||
// Cell pointer and cell bit mask for first cell in row
|
||
base_cell_ptr = row_cell_ptr; // to access upper left neighbor
|
||
base_mask = 0x01; // of first cell in row
|
||
for (x=0; x<width; x++) { // repeat for each cell in row
|
||
// First, count neighbors
|
||
// Point to upper left neighbor of current cell
|
||
cell_ptr = base_cell_ptr; // pointer and bit mask for
|
||
mask = base_mask; // upper left neighbor
|
||
// Count upper left neighbor
|
||
neighbor_count = (*cell_ptr & mask) ? 1 : 0;
|
||
// Count left neighbor
|
||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||
// Count lower left neighbor
|
||
if ((*(cell_ptr + width_in_bytesX2) & mask))
|
||
neighbor_count++;
|
||
// Point to upper neighbor
|
||
if ((mask >>= 1) == 0) {
|
||
mask = 0x80;
|
||
cell_ptr++;
|
||
}
|
||
// Remember where to find the current cell
|
||
current_cell_ptr = cell_ptr + width_in_bytes;
|
||
current_mask = mask;
|
||
// Count upper neighbor
|
||
if ((*cell_ptr & mask)) neighbor_count++;
|
||
// Count lower neighbor
|
||
if ((*(cell_ptr + width_in_bytesX2) & mask))
|
||
neighbor_count++;
|
||
// Point to upper right neighbor
|
||
if ((mask >>= 1) == 0) {
|
||
mask = 0x80;
|
||
cell_ptr++;
|
||
}
|
||
// Count upper right neighbor
|
||
if ((*cell_ptr & mask)) neighbor_count++;
|
||
// Count right neighbor
|
||
if ((*(cell_ptr + width_in_bytes) & mask)) neighbor_count++;
|
||
// Count lower right neighbor
|
||
if ((*(cell_ptr + width_in_bytesX2) & mask))
|
||
neighbor_count++;
|
||
if (*current_cell_ptr & current_mask) {
|
||
if ((neighbor_count != 2) && (neighbor_count != 3)) {
|
||
*(dest_cell_ptr + (current_cell_ptr - cells)) &=
|
||
~current_mask; // turn off cell
|
||
draw_pixel(x, y, OFF_COLOR);
|
||
}
|
||
} else {
|
||
if (neighbor_count == 3) {
|
||
*(dest_cell_ptr + (current_cell_ptr - cells)) |=
|
||
current_mask; // turn on cell
|
||
draw_pixel(x, y, ON_COLOR);
|
||
}
|
||
}
|
||
// Advance to the next cell on row
|
||
if ((base_mask >>= 1) == 0) {
|
||
base_mask = 0x80;
|
||
base_cell_ptr++; // advance to the next cell byte
|
||
}
|
||
}
|
||
row_cell_ptr += width_in_bytes; // point to start of next row
|
||
}
|
||
}
|
||
|
||
Listing 17.4 and Listing 17.3 are functionally the same; the only
|
||
difference lies in how **next\_generation()** is implemented. (Only
|
||
**next\_generation()** is shown in Listing 17.4; the program is
|
||
otherwise identical to Listing 17.3.) Listing 17.4 applies the following
|
||
optimizations to **next\_generation()**:
|
||
|
||
The neighbor-counting code is brought into **next\_generation,**
|
||
eliminating many function calls and from-scratch address/mask
|
||
calculations; all multiplies are eliminated by using pointers and
|
||
addition; and all cells are accessed directly via pointers and masks,
|
||
eliminating all remaining function calls and from-scratch address/mask
|
||
calculations.
|
||
|
||
The net effect of these optimizations is that Listing 17.4 is more than
|
||
twice as fast as Listing 17.3; we’ve achieved the desired 18 generations
|
||
per second, albeit only on a 486, and only at 96x96. (The **\#define**
|
||
that enables code limiting the speed to 18 Hz, which seemed ridiculous
|
||
in Listing 17.1, is actually useful for keeping the generations from
|
||
iterating too quickly when Listing 17.4 is running on a 486, especially
|
||
with a small cellmap like 48x48.) We’ve sped things up by about eight
|
||
times so far; we need to increase our speed another ten times to reach
|
||
our goal of 200x200 at 18 generations per second on a 20 MHz 386.
|
||
|
||
It’s undoubtedly possible to improve the performance of Listing 17.4
|
||
further by fine-tuning the code, but no tremendous improvement is
|
||
possible that way.
|
||
|
||
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||
 *Once you’ve reached the point of fine-tuning pointer usage and register variables and the like in C or C++, you’ve become compiler-dependent; you therefore might as well go to assembly and get the real McCoy.*
|
||
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
------------------------ --------------------------------- --------------------
|
||
[Previous](17-04.html) [Table of Contents](index.html) [Next](17-06.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
* * * * *
|
||
|
||
Graphics Programming Black Book © 2001 Michael Abrash
|