Book Review:Efficient C++:Performance Programming Techniques

Book Cover

Dov Bulka and David Mayhew
Addison–Wesley
Reading, MA, 2000
336 pp., $34.95
ISBN 0-201-37950-3

Does software efficiency matter any more, with Moore's law doubling the speed of processors every 18 months? In the introduction to Efficient C++, Bulka and Mayhew argue that performance will always be important. The hardware may grow ever faster, but the demands placed upon it grow apace. Today's applications cripple yesterday's machine. Particularly for high-volume server applications, more efficient software will mean more affordable hardware solutions.

Efficient C++ does a good job of highlighting the performance pitfalls of C++ that have left the language with a poor reputation in some circles. In C, there is usually a clear mapping between the source code and the object code produced by the compiler. In C++, with constructors, destructors, overloaded operators, and compiler-generated temporaries, it can be hard to tell if a line of code will generate four instructions or four hundred.

At 300 pages of relatively large type, this is a short book. While brevity is preferable to yet another 1000-page tome, perhaps it's too short; I felt that many of the topics were underexplored. The authors do not tie themselves to a particular compiler or operating system. This means that the book will not be obsolete in 12 months, but it also means that they do not talk about platform-specific tools, such as profilers, except in the most general terms.

The book opens with a war story about tracing (debug output), which nicely illustrates many of the issues they dive into in later chapters. They move on to constructors and destructors, virtual functions (runtime polymorphism) and templates (compile-time polymorphism), the return value optimization, and temporaries (one omission: operator++(int) (post-increment) is more expensive than pre-increment, because a temporary must be generated to hold the return value). There are two chapters on memory pooling—custom allocators can be much faster than the global operator new and operator delete—and they show several implementations.

The book continues with three very good chapters on inlining: why, when, when not, and how best to inline. As inlining is one of the key C++ performance tuning techniques, this is especially valuable. There is one chapter on the standard template library, which describes the performance characteristics of the containers, mentions function objects, almost completely omits any discussion of the STL iterators or algorithms, and concludes with a description of how you might pro-duce a custom algorithm that's better than accumulate() but points out that the effort is rarely worthwhile.

Reference counting is next, followed by a chapter each on coding optimizations and design optimizations. Both optimization chapters deserved fuller treatment, since these topics alone could fill a book. (Jon Bentley's classic Writing Efficient Programs,1 now sadly out of print, is an excellent treatise on coding optimizations.)

I particularly liked the last two chapters in the book: multiprocessor scalability and system architecture dependencies. Like the authors, I have a lot of experience with building Web servers, and find that these issues are underappreciated: Software components designed for the desktop often run poorly on servers. Modern CPUs have become much faster than RAM and need at least two layers of memory cache to run efficiently. Just as earlier generations of programmers needed to minimize page faults to prevent crippling their performance, modern programmers need to minimize cache faults. (Page faults are less likely nowadays with the huge memories in modern machines, but they consume more cycles than ever.) Making multithreaded programs scale well on symmetric multiprocessor (SMP) machines requires an appreciation of SMP architecture to avoid cache consistency storms and scaling bottlenecks.

The book is clearly written and gets its points across without miring the reader in C++ arcana. At the end of each chapter there is a useful summary of the key points. It's pithy too: "He who fails to design for performance, performs a design failure."

The book is quite good as far as it goes, but there are a number of surprising omissions. Exceptions are only mentioned briefly during a discussion of inlining. Enabling exceptions has costs—keeping track of which objects may need to be destroyed if the stack unwinds, not to mention handling the exception if one is thrown—and benefits—reducing error checking in the mainline of the code. A great deal of uncertainty still surrounds exceptions and their performance characteristics should have been covered.

Much of the STL is ignored. The rest of the standard library is not covered at all, which is an important omission because even basic building blocks like strings and streams can easily be misused.

Most of the book talks about time efficiency, relegating discussion of space efficiency to the back burner. While the huge memories of modern computers render space efficiency much less important than it used to be, it will always be an issue. Working set sizes need to be kept under control, so that multiple programs can cohabit peaceably and so that programs can scale to larger workloads. At Microsoft, we find that optimizing for space is more effective than optimizing for speed, because it allows processor caches to be more effective.

Big-O notation (asymptotic complexity) is given short shrift. Not all C++ programmers have a formal computer science background, and understanding the difference between, say, O(N) and O(N2) runtime is important to understanding how well an algorithm scales to large values of N. Obviously, it's also important to know the constant factors that big-O notation hides—something they do emphasize.

I would have liked to see a much deeper discussion of performance measurement and diagnosis. The authors limit themselves to timing 1,000,000 iterations of code fragments and a perfunctory description of profiling. They give no hints on how you might measure the performance of an event-driven program (e.g., almost anything with a GUI), a multithreaded program, or a distributed program. Poorly drawn diagrams and a number of typos and misspellings also mar the book.

Verdict: Recommended with reservations. Beginning and intermediate C++ programmers will definitely benefit from reading this book, despite its omissions. Advanced programmers should find it useful to have performance information collected in one place.

Reference

  1. Bentley, J. Writing Efficient Programs, Prentice-Hall, Englewood Cliffs, NJ, 1982.

About the Author

George V. Reilly works on the Internet Information Services development team at Microsoft, where he is in charge of performance. He has been doing his best to write tight code since he discovered the BBC Micro in his native Dublin, Ireland, in 1982. He is a coauthor of Beginning ATL 3 COM Programming and Professional Active Server Pages 3.0, both Wrox Press, 1999. George may be contacted at [email protected].