John Carmack, a name synonymous with innovation in game development and particularly renowned for his groundbreaking work at id Software, is not just a brilliant programmer but also a thoughtful commentator on software engineering practices. This article delves into a fascinating email from Carmack dating back to 2007, where he discusses coding style, specifically advocating for inlining code. His perspectives, still relevant today, offer valuable lessons for developers aiming for performance, reliability, and a deeper understanding of their codebase.
The Case for Inlining: Awareness and Control
Carmack begins his discourse by recalling a discussion about ultra-reliable aerospace software. An intriguing anecdote about the Saab Gripen fighter jet’s fly-by-wire system—which remarkably disallowed subroutine calls and backward branches except for the main loop—ignited Carmack’s experimentation. Inspired by the clarity and control this approach offered in critical systems, he applied a similar principle to the Armadillo rocket’s flight control code: inlining all subroutines within the main tick function.
While not unearthing any catastrophic bugs, this exercise proved insightful. Carmack discovered redundant variable assignments and questionable control flow patterns, ultimately leading to cleaner and more concise code. This experience shifted his perspective, prompting him to consider the broader application of inlining within id Software’s game development.
Exploring Coding Styles: From Functions to Inlining
Carmack outlines three coding styles to illustrate his point:
Style A (Traditional Functional Decomposition):
void MinorFunction1( void ) { }
void MinorFunction2( void ) { }
void MinorFunction3( void ) { }
void MajorFunction( void ) {
MinorFunction1();
MinorFunction2();
MinorFunction3();
}
Style B (Variation on Functional Decomposition):
void MajorFunction( void ) {
MinorFunction1();
MinorFunction2();
MinorFunction3();
}
void MinorFunction1( void ) { }
void MinorFunction2( void ) { }
void MinorFunction3( void ) { }
Style C (Inlining Approach):
void MajorFunction( void ) {
// MinorFunction1
// MinorFunction2
// MinorFunction3
}
Historically, Carmack favored Style A for readability. However, he argues for the merits of Style C, the inlining approach, not for direct performance gains from avoiding function calls, but for its impact on the development process and code comprehension.
Stepping Through the Frame: The Debugging Advantage
Carmack emphasizes the importance of “stepping a frame” during debugging – tracing the complete code execution path from a major point in the game loop. He notes how easily developers can overlook large code blocks, even with performance and stability implications. Inlining, in this context, fosters a greater awareness of the entire execution flow. By having the code laid out sequentially, developers are less likely to unintentionally bypass critical sections during debugging and optimization.
He also points out that C++, with its features like operator overloading and implicit constructors, can obscure the actual code execution. Games, with their real-time constraints and recurring tic structure, benefit from a programming style that enhances clarity and reduces hidden complexities.
Latency and Performance Consistency
Carmack highlights how operational latency can subtly creep into game code when operations are deeply nested within subsystems. Inlining helps mitigate this by making the execution order within a frame explicitly visible. When everything executes within a larger, sequential function, it becomes immediately apparent which operations occur first, ensuring timely execution within the frame rendering process.
Furthermore, he argues against excessive conditional operations aimed at optimizing average case performance. While seemingly beneficial for demo metrics, this approach can introduce bugs by inadvertently skipping essential state updates. Carmack advocates for prioritizing worst-case performance and consistent frame times, even if it means executing operations that might sometimes be redundant. An “execute-and-inhibit” strategy, where operations are always performed but their results conditionally ignored, can lead to more stable and predictable performance.
Modularity vs. Awareness: A Trade-off
Carmack acknowledges the inherent conflict between inlining and modularity, a cornerstone of object-oriented programming. While modularity aims to encapsulate details, inlining promotes a broader awareness of the codebase. He suggests that heavyweight objects can serve as reasonable boundaries for code combination, advocating for reducing the reliance on medium-sized helper objects and striving for purely functional lightweight objects when necessary.
Key Takeaways: Carmack’s Inlining Principles
Carmack summarizes his thoughts with actionable principles:
- Inline Single-Call Functions: If a function is called only once, inlining is strongly recommended.
- Consolidate Work for Multi-Call Functions: For functions called from multiple locations, explore consolidating the work into a single place, possibly using flags, and then inline it.
- Parameterization over Multiple Functions: Instead of having multiple versions of a function, consider creating a single, more parameterized function with default values.
- Embrace Functional Purity: When feasible, strive for purely functional code with minimal global state references.
- Utilize
const
: Employconst
for parameters and functions when functions must be used in multiple contexts to minimize side effects. - Minimize Control Flow Complexity: Favor consistent execution paths over overly optimized conditional execution to reduce bugs and improve performance predictability.
Conclusion: A Pragmatic Approach to Code Style
John Carmack’s email on inlining code offers a valuable perspective on coding style, particularly within the demanding context of game development at id Software. His advocacy for inlining is not a blanket recommendation but a nuanced approach rooted in practical experience and a deep understanding of software engineering trade-offs. By prioritizing code awareness, reducing hidden complexities, and ensuring consistent performance, Carmack’s insights continue to resonate with developers striving for robust and efficient software. His emphasis on understanding the full execution flow and minimizing unexpected state mutations remains a timeless principle in software development.