He who has a why can endure any how” - Friedrich Nietzsche
There are many domains such as embedded and kernel development where high level memory abstractions are insufficient. For individuals with interests outside of these domains it’s quite possible to make the argument that learning the particulars of computer memory is an absurd endeavor. However, that is a somewhat myopic perspective.
The truth is, unlike our predecessors, a deep understanding of machine architecture is no longer requite to perform useful computation. It’s entirely possible to create elegant software without even a parochial conception of computer memory. High level programming languages have all but abstracted away the underlying machine. The barriers to entry have never been lower. The average person with a year of training can produce software today that required a PhD level expert thirty years ago. The proliferation of high level languages has provided benefits too numerous to count. Each new layer of abstraction stacked on top of machines promises more rapid development with fewer defects. Arguing against these advances is a formidable task that only a fool would undertake. The intent is not to discourage the use of memory abstractions. Rather, the intent is to make the case that understanding what lies beneath is worthwhile.
There are obvious arguments in favor of deeper exploration such as simple erudition and curiosity. Honorable motivations indeed; however, there are deeper justifications that provide more concrete benefits. First of all, abstractions are never perfect because they provide a simplified view of reality. A programmer devoid a conception of reality is incapable of effectively troubleshooting problems. It’s analogous to the difference between being able to drive a car and repair one. The same is true of performance tuning. Simplification often comes at the price of processor cycles. This is typically an acceptable trade off. As the great Donald Knuth said, “premature optimization is the root of all evil”. However, when performance is requite, there is no escaping the need for deeper level knowledge.
The majority of modern programming consists of gluing pre-made constructs together. There is obvious benefit to understanding their internal workings. However, of greater concern is what happens when there are no abstractions that meet the current need. Many developers will resort to contorting existing generalizations. Without deeper understanding, they are beholden to developers without knowledge of their domain. Unique problems call for unique solutions and it’s difficult to create algorithms and data structures that fully harness the power of the machine without a grasp of how the machine works. The most notable reason to understand memory is that it will change the way you write software. As this section opens with a Nietzsche quote, it seems appropriate to invoke his genius once again: “if thou gaze long into an abyss, the abyss will also gaze into thee”. The meaning being that you will become what your mind focuses on. With an almost unconscious effort, simply understanding improves software quality.
To sum up the why, any developer ignorant of what lies beneath is constrained by the simplified view the abstraction exposes and therefore a slave to it. Good programmers are able to bend high-level constructs to their will. Great programmers, in times of trouble, are able to lift the veil to reveal a more accurate picture of reality. Exceptional programmers create abstractions that harness the full power of the machine. The question the reader must ask themselves is what kind of programmer they wish to be.
How Deep is Deep Enough?
“To understand recursion, one must first understand recursion” – Stephen Hawking
The previous section extoled the virtues of peering beneath abstractions. While the reasoning is valid, beware that it’s possible to extend the concept to absurdity. At some level, programmers are always dealing with simplified generalizations. Application programmers have high level languages. Compiler programmers have assembly language. Assembly language engineers have machine code. Processor designers have micro-code. There are even countless levels of abstraction before, after, and between each of those afore mentioned. Constantly digging underneath will find a person quickly recursing into oblivion. Learning programming by starting with atomic theory is a rather counterproductive pursuit.
This begs the question, how deep is deep enough? The answer is more philosophic than technical and each individual must surely answer for themselves. As Jean-Paul Sartre wrote, “man [and woman] is condemned to be free”. Decisions affecting one’s career are a personal responsibility. However, even though philosophic questions are outside the purview of this blog, the recommended approach is a practical one: go as deep as is useful. Take the time to get a cursory understanding of the layer immediately below the one in which you commonly work. It’s not possible to know if it’s useful without at least some exploration. Let instinct be a guide on how much further to go.
The intent of this article is not to dive to perfect depth. As stated, that’s dependent on the individual. This piece should provide a reasonable starting point for any programmer interested in learning about computer memory. For some, this alone will be sufficient to improve their programming skills. It will act as a starting point for those with a more voracious thirst. With that, it’s time to dispense with the philosophical and address the purpose directly.