This article is a continuation of Minimizing Code Bloat for Faster Builds and Smaller Executables.
Static allocations are certainly the simplest and most predictable sources of code bloat. Static allocations refer to data structures in a program for which storage is allocated for the entire duration of the application’s execution. In C++ file, class, and local static variables all require static allocations. Additional examples of static allocations in C++ are global variables, string literals, and virtual function tables.
Static allocations can be very useful in some circumstances, but they can also be very wasteful. Most of the code in an engine is executed very infrequently, which means most of the static allocations supporting that code are rarely needed. Unfortunately since static allocations exist for the lifetime of the program, they’re taking up memory whether you’re using them or not.
In addition to possibly wasting memory, static allocations can increase program link and load times. The memory for most static allocations is reserved directly within the application binary. Consequently, every megabyte of static allocation is a megabyte that must be written to disk every time you link, copied across the network every time you deploy, and read from disk every time you launch.
The one exception to this is a special kind of static allocation known as bss data. Static allocations that are known at compile time to be zero-initialized are placed in a special section of the executable called the bss section. Memory used by objects in the bss section isn’t reserved in the executable, instead it is allocated by the program loader. Bss data doesn’t significantly impact build times, but it has the same run time memory requirements as other static allocations.
In the list of code bloat causes, static allocations are usually a very distant third, but every engine I’ve investigated for static allocations has yielded a few unpleasant surprises.
Dumpbin isn’t of much help when it comes to tracking static allocations. Data symbols aren’t packaged as COMDATs, so they don’t show up in the /headers report I’m so fond of. To gather statistics on the static allocations within your application, you’ll have to crack open the PDB. Every static and global variable is documented in the program database along with its size and, through some creative cross-referencing, its source file.
Large static objects are easy to find–just sort the data symbols by size and gawk at the top offenders. Don’t stop there though. If you generate a lot of code with templates or macros, you may have large numbers of relatively small static objects. To quantify the cost of these objects you’ll need to do some pattern matching on symbol names. You can strip template parameters from symbols so MyClass<int>::s_buffer and MyClass<float>::s_buffer get counted together in the stats, or you can strip away the scope entirely and tally the stats for all statics named s_buffer together. I find it useful to analyze the results from collapsing symbol names in a variety of different ways. Which option yields the best results depends on the particular codebase I’m dealing with and the type of code generation it uses.
That covers it for static allocations and for executable size optimizations in general. I still have a couple topics to go, but from now on they’re relevant to build times alone. Coming up, incorrect inlining.