abrash-black-book/17-01.md
2013-12-30 20:26:41 +11:00

89 lines
No EOL
4.8 KiB
Markdown

Chapter 17\
The Game of Life {#Heading1}
-----------------
### The Triumph of Algorithmic Optimization in a Cellular Automata Game {#Heading2}
I've spent a lot of my life discussing assembly language optimization,
which I consider to be an important and underappreciated topic. However,
I'd like to take this opportunity to point out that there is much, much
more to optimization than assembly language. Assembly is essential for
absolute maximum performance, but it's not the only ingredient;
necessary but not sufficient, if you catch my drift—and not even
necessary, if you're looking for improved but not maximum performance.
You've heard it a thousand times: Optimize your algorithm first. Devise
new approaches. Or, as Knuth said, *Premature optimization is the root
of all evil.*
This is, of course, old hat, stuff you know like the back of your hand.
Or is it? As Jeff Duntemann pointed out to me the other day, performance
programmers are made, not born. While I'm merrily gallivanting around in
this book optimizing 486 pipelining and turning simple tasks into
horribly complicated and terrifyingly fast state machines, many of you
are still developing your basic optimization skills. I don't want to
shortchange those of you in the latter category, so in this chapter,
we'll discuss some high-level language optimizations that can be applied
by mere mortals within a reasonable period of time. We're going to
examine a complete optimization process, from start to finish, and what
we will find is that it's possible to get a 50-times speed-up without
using *one byte of assembly!* It's all a matter of perspective—how you
look at your code and data.
### Conway's Game {#Heading3}
The program that we're going to optimize is Conway's famous Game of
Life, long-ago favorite of the hackers at MIT's AI Lab. If you've never
seen it, let me assure you: Life is *neat,* and more than a little
hypnotic. Fractals have been the hot graphics topic in recent years, but
for eye-catching dazzle, Life is hard to beat.
Of course, eye-catching dazzle requires real-time performance—lots of
pixels help too—and there's the rub. When there are, say, 40,000 cells
to process and display, a simple, straightforward implementation just
doesn't cut it, even on a 33 MHz 486. Happily, though, there are many,
many ways to speed up Life, and they illustrate a variety of important
optimization principles, as this chapter will show.
First, I'll describe the ground rules of Life, implement a very
straightforward version in C++, and then speed that version up by about
eight times without using any drastically different approaches or any
assembly. This may be a little tame for some of you, but be patient; for
after that, we'll haul out the big guns and move into the 30 to 40 times
speed-up range. Then in the next chapter, I'll show you how several
programmers *really* floored it in taking me up on my second
Optimization Challenge, which involved the Game of Life.
#### The Rules of the Game {#Heading4}
The Game of Life is ridiculously simple. There is a cellmap, consisting
of a rectangular matrix of cells, each of which may initially be either
on or off. Each cell has eight neighbors: two horizontally, two
vertically, and four diagonally. For each succeeding generation of
cells, the game logic determines whether each cell will be on or off
according to the following rules:
- If a cell is on and has either two or three neighbors that are on in
the current generation, it stays on; otherwise, the cell turns off.
- If a cell is off and has exactly three "on" neighbors in the current
generation, it turns on; otherwise, it stays off. That's all the
rules there are—but they give rise to an astonishing variety of
forms, including patterns that spin, march across the screen, and
explode.
It's only a little more complicated to implement the Game of Life than
it is to describe it. Listing 17.1, together with the display functions
in Listing 17.2, is a C++ implementation of the Game of Life, and it's
very straightforward. A cellmap is an object that's accessible through
member functions to set, clear, and test cell states, and through a
member function to calculate the next generation. Calculating the next
generation involves nothing more than using the other member functions
to set each cell to the appropriate state, given the number of
neighboring on-cells and the cell's current state. The only complication
is that it's necessary to place the next generation's cells in another
cellmap, and then copy the final result back to the original cellmap.
This keeps us from corrupting the current generation's cellmap before
we're done using it to calculate the next generation.
All in all, Listing 17.1 is a clean, compact, and elegant implementation
of the Game of Life. Were it not that the code is as slow as molasses,
we could stop right here.