Towards writing my Jamca chess engine (in C++), I decided that I need some insights into efficient C++ programming. Though I was always generally aware of the issues involved, I wanted some expert advice. This book is "highly recommended" on ACCU, so it was an immediate pick.
The scope of this book is quite large, though the book itself is relatively thin (~200 pages). This is always a good sign - I hate over-bloated tomes. Many C++ specific topics are discussed - inlining, constructors/destructors, virtual functions, memory allocation, STL. Other, less C++ and more general efficiency issues are presented as well: caching, lazy evaluation, various design optimizations, scalability to multi-processor machines, system architecture, etc.
The chapters dealing with home-grown memory pooling are terrific. A complete memory manager is developed in incremental steps - it is a truly educative read, even for someone who implemented these things before. The authors' incremental approach, ready to "throw code away" makes sure that the implementation is gradually improved and the reader is exposed to the improvement process, gaining understanding of what problems are being solved. The memory manager is later extended to a multi-threaded version, something that I've never had to work with, so it was an even more instructional for me.
A good example of the authors' great and "honest" writing style is the chapter on STL. After various popular operations (insert, delete, find, traverse) are discussed and compared on different containers, the authors question the possibility of "outperforming" the STL with a home-grown solution. They provide a candid effort
to write a faster accumulator and show how it doesn't work. Then, they consider a more contrived example - where domain specific knowledge helps their solution to outperform STL. The point they make out of it is accurate: you can't generally outperform the STL, unless you have some domain specific knowledge that the STL doesn't. Some performance considerations in the implementation of the list size() operator are discussed to show the performance tradeoffs in STL's design.
This reminds me of a minor downside of the book: the balance between inlining and STL is, IMHO the opposite of what it's supposed to be. The authors dedicate 3 chapters to inlining, and only one to the STL, while I think that one chapter to inlining and 3 to the STL would be more appropriate. After all, inlining is mostly something done by the compiler (and the authors mention it several times), while smart usage of the STL (which is in the programmer's, rather than in the compiler's domain) may bring considerable performance improvements. Perhaps the STL chapter was so enjoyable that it just made me want some more :-)
But back to the praises... The book features a fair and interesting discussion about the tradeoff between software performance and flexibility (in focus in chapter 14, but spreads to other chapters as well). Software (and especially libraries, like the STL) should be made as flexible as possible, that's a long known fact. But one should recognize that flexibility sometimes drags a performance cost behind it. Flexibility equals minimum assumptions about the data, while some application-specific information about the data may greatly aid in performance. The authors suggest to always write flexible code and use generic libraries, but when profiling shows that some of the flexible routines are slow, it may be time to say goodbye to flexibility in these routines and make them more domain-specific.
To conclude, this is an excellent book. Well written, presents important topics and explains them clearly. Highly recommended for any programmer intending to write efficient C++.
Update 09.01.2010: In an attempt to refresh my C++ skills, I've taken another look at this book, only examining chapter 6 (single-threaded memory pooling). I'm a bit less excited about it now, as I've noticed two problems in the chapter:
- The benchmark performed of the built-in versus custom allocator is skewed, taking advantage of a rather peculiar allocation strategy of the user code. It would be much better to allocate a large chunk at a time, gaining performance even when there are a lot of allocations without much releasing.
- The code is geared towards the non-standard MSVC 6 (two consecutive
for
loops with only the first defining int i
is a sure MSVC 6 "smell") and probably won't compile on a standards-abiding C++ compiler.
These problems don't make the book bad, but teach us that everything should be taken with a grain of salt. There's no replacement for common sense and experience.