Performance advice in programming often arrives as a set of neat rules. The stack is fast. Memory access is constant time. Contiguous containers are cache friendly. More threads make a program faster.

There is usually some truth behind these statements. That is why they survive. The trouble starts when the explanation around them disappears.

A rule that originally described a particular situation gets repeated without its assumptions. Eventually, learners remember the rule but not the conditions that made it useful. What began as a simplification becomes folklore.

For anyone teaching systems programming or performance engineering, this creates a genuine problem. We cannot begin by explaining the entire computer.

A learner who is trying to understand a programming language may already be dealing with types, control flow, functions, objects, memory and concurrency. Add operating systems, compilers, caches, virtual memory and processor architecture, and the amount of information they need to coordinate grows quickly.

Simplification is not bad teaching. It is often necessary. The question is what happens after the first simplified explanation.

Technical illustration contrasting simplified software models with measured CPU, cache and performance behaviour

A useful model should have somewhere to go

Consider array access.

Teaching that accessing an element in an array is an O(1) operation is perfectly reasonable. It gives someone a useful algorithmic model without first requiring them to understand memory hierarchies or processor architecture.

Later, they can return to the same operation and look at memory layout. Then cache locality. Then paging. Then prefetching. Eventually, they may start thinking about TLB behaviour, NUMA effects or the instructions generated by the compiler.

The first explanation has not suddenly become wrong. It answered a particular question. What changes is the depth of the question the learner is now able to ask.

This is closely aligned with Jerome Bruner’s idea of the spiral curriculum. Bruner argued that important concepts can be introduced in an accessible form and revisited later with greater depth. The learner returns to the same underlying idea, but each pass adds complexity and connects new knowledge to what came before.

Howard Johnston later summarised the spiral curriculum around three features: returning to a topic repeatedly, increasing its complexity, and linking each new encounter to previous learning.

That is a useful way to think about systems education. We do not have to choose between giving beginners a crude explanation and exposing them to the entire machine. We can teach progressively.

I use the phrase boundary-aware abstraction to describe how I apply that idea to systems teaching. It is not a new theory of learning. It is a design principle.

Start with a model that helps the learner reason about something useful. Be clear about what the model assumes. Make it possible to discover where those assumptions stop being sufficient. Then provide a route into the next layer.

The abstraction is doing its job if it helps someone think. It becomes a problem when it is mistaken for the whole system.

Let the learner test the model

Progression alone is only part of the picture. Learners also need a reason to revise what they currently believe.

Piaget’s work on cognitive development described learning in terms that included assimilation and accommodation. Sometimes a new experience fits comfortably into an existing mental structure. At other times, the experience does not fit, and the structure itself has to change.

Computer systems provide plenty of opportunities for that kind of conflict.

Imagine a learner who believes that two threads modifying two different variables should operate independently. That is not an unreasonable starting model.

Now suppose they run a benchmark and discover that changing the position of those variables in memory produces a substantial performance difference. Their explanation no longer accounts for the result.

At that point, there is something to investigate. Why should the location of two independent variables affect their performance?

The benchmark has created a gap between prediction and observation. That gap can drive the next stage of learning.

Instead of beginning with a lecture about cache-coherence protocols, the learner begins with a model they already understand. The machine then gives them evidence that the model is incomplete. They now have a reason to learn what sits underneath it.

The process can be expressed simply: model → predict → experiment → observe → explain → revise.

Horizontal observable learning cycle from building a model through prediction, experiment and observation to explanation and model revision
Observable learning as a feedback cycle: model, predict, experiment, observe, explain and revise.

Once the model has been revised, the same process can happen again at a deeper level. A learner may first explain false sharing by talking about cache lines. Later, they can return to the same phenomenon and study coherence protocols, processor topology, thread placement or NUMA behaviour.

The concept has not been replaced. It has grown.

Guidance still matters

There is an obvious weakness in discovery-based approaches if they are handled badly. Giving someone an unexplained performance result and telling them to work it out is not automatically good teaching.

A learner needs enough support to make useful progress.

Vygotsky’s concept of the zone of proximal development is relevant here. It focuses attention on the space between what someone can currently achieve independently and what they can achieve with appropriate support.

The idea of scaffolding, developed explicitly by Wood, Bruner and Ross in their work on tutoring, adds another useful perspective. Support can help a learner perform reasoning that would initially be beyond them, but that support should not become permanent.

In a systems lesson, early scaffolding might be quite explicit. A diagram could show two variables occupying the same cache line. Later, the diagram can disappear and the learner can inspect addresses for themselves. Later still, they might use a profiler, examine hardware counters or decide independently what measurements are needed.

The destination is not dependence on a visualisation. It is the ability to investigate unfamiliar behaviour without being told exactly what to look for.

Mastery is not the same as reaching the end of a topic

Benjamin Bloom approached progression from a different direction. His work on mastery learning challenged the idea that teaching something once and then testing it should mark the end of learning.

Assessment can expose weaknesses while there is still time to correct them.

That idea fits systems education particularly well because knowing terminology is not the same as having a useful mental model. A learner might correctly define cache locality but still be unable to predict which of two data layouts is likely to make better use of the cache.

Someone may be able to describe false sharing after seeing an example, then fail to recognise the same mechanism when the variables are buried inside a larger structure.

A stronger test of understanding asks whether someone can use the idea. Can they explain what is happening? Can they make a sensible prediction? Can they recognise the mechanism in an unfamiliar program? Can they identify conditions where the rule no longer applies?

When the answer is no, repeating the same explanation is unlikely to fix the problem. The learner needs another opportunity to examine the concept from a different angle.

Assessment then becomes part of learning rather than simply the point where learning is judged.

Being wrong should produce information

Carol Dweck’s work on growth mindset also influenced my own educational philosophy. The part that stayed with me was not the slogan that anyone can achieve anything through effort. That would be a poor reading of the evidence.

It was the idea that difficulty does not have to be interpreted as proof of fixed ability.

The modern evidence around growth-mindset interventions is more complicated than some early educational interpretations suggested. Yeager and Dweck (2020) themselves acknowledge that effects vary across people and contexts. Macnamara and Burgoyne (2023) found very small effects on academic achievement in their systematic review and meta-analysis and raised concerns about study quality and publication bias.

That makes strong claims about attainment difficult to justify. There is still a useful educational principle here.

An incorrect prediction can be productive. If someone expects a benchmark to behave one way and the machine behaves differently, the learner has not wasted the experiment. They have discovered a weakness in their current explanation.

The useful question is not Why didn’t I know this? It is What does my current model fail to explain?

That is much closer to the way engineering works in practice.

Observable does not simply mean visual

The idea of observable systems can easily be misunderstood as an argument for more diagrams and animations. Those can help. They are not the point.

Research into algorithm and program visualisation repeatedly shows that simply showing learners an animation is not enough.

Hundhausen, Douglas and Stasko (2002) found that learner engagement played an important role in the effectiveness of algorithm visualisation. Sorva, Karavirta and Malmi (2013) reached a similar conclusion when reviewing program-visualisation systems, highlighting the importance of how learners interact with the representation and how it fits into teaching.

Scherer, Siddiq and Sánchez Viveros (2020) later reported a positive average effect across visualisation interventions, although that does not tell us that a particular cache animation or memory diagram will improve systems learning.

A better principle is to give learners access to evidence they can reason about.

Sometimes that evidence will be visual. Sometimes it will be a benchmark result. It could be a memory address, compiler output, generated assembly, a flame graph, an allocation trace or a hardware counter.

What matters is what the learner does with it. Watching a cache animation is passive. Predicting what the cache will do, observing the result and explaining why the prediction succeeded or failed requires a model.

Cognitive load should shape what we reveal

Progressive explanation also gives us a better way to think about cognitive load.

A single performance problem can involve compiler optimisation, memory layout, thread scheduling, cache behaviour and processor architecture. Expecting someone to reason accurately across all of those mechanisms from the start is demanding.

Research into cognitive load within programming education supports treating that difficulty seriously. Berssanette and de Francisco (2022) found widespread use of cognitive load theory in programming education research. Duran, Zavgorodniaia and Sorva (2022) also found that the theory was sometimes invoked without sufficiently precise hypotheses or measurements.

That warning is worth paying attention to. Saying that something creates “too much cognitive load” can become another vague rule.

The answer is not simply to remove complexity. The better question is which complexity the learner needs now.

Details that are not yet useful can remain hidden, provided they have not been erased from the explanation entirely.

A learner may initially be told that two variables share a cache line. Later, they can ask what a cache line actually means in that context. Later still, they may discover that std::hardware_destructive_interference_size is not simply a direct measurement of the processor’s physical cache-line size.

Each explanation prepares the ground for the next one.

What this means for systems education

I do not think we should stop teaching rules of thumb. They are useful. I think we should be more careful about how we teach them.

When we say that something is fast, we should explain which operation and which conditions we mean. When an explanation is deliberately simplified, we should say so. Language guarantees should be separated from compiler behaviour, operating-system behaviour and hardware observations.

Learners should be asked to predict outcomes before being shown them. Measurements should be treated as evidence to examine, not answers to copy down. Unexpected results should open another question rather than end the experiment. Important ideas should return throughout the curriculum as learners become capable of reasoning about deeper mechanisms.

This is where Bruner, Piaget, Vygotsky, Bloom and the part of Dweck’s work that shaped my own teaching start to converge. They do not say the same thing, and they come from different traditions that should not be collapsed into one theory. Together, though, they support a coherent approach.

Start with what the learner can currently understand. Connect new ideas to the model they already have. Give them structured support where they need it. Let evidence expose weaknesses in the model. Then return to the concept with greater depth.

For systems programming, that leads to a principle I find more useful than trying to eliminate abstraction:

We should build abstractions that learners can test, revisit and progressively refine as their understanding develops.

The whole machine does not need to be visible at once. There does need to be a path towards it.

The companion article, From Prediction to Evidence: Teaching False Sharing as an Observable System, puts this approach into practice using one deceptively simple performance problem.

References

  • Berssanette, J. H., & de Francisco, A. C. (2022). Cognitive load theory in the context of teaching and learning computer programming: A systematic literature review. IEEE Transactions on Education, 65(3), 440–449. https://doi.org/10.1109/TE.2021.3127215
  • Bloom, B. S. (1968). Learning for mastery. Evaluation Comment, 1(2), 1–12.
  • Bruner, J. S. (1960). The process of education. Harvard University Press.
  • Duran, R., Zavgorodniaia, A., & Sorva, J. (2022). Cognitive load theory in computing education research: A review. ACM Transactions on Computing Education, 22(4), Article 40, 1–27. https://doi.org/10.1145/3483843
  • Hundhausen, C. D., Douglas, S. A., & Stasko, J. T. (2002). A meta-study of algorithm visualization effectiveness. Journal of Visual Languages & Computing, 13(3), 259–290. https://doi.org/10.1006/jvlc.2002.0237
  • Johnston, H. (2012). The spiral curriculum. Education Partnerships, Inc.
  • Macnamara, B. N., & Burgoyne, A. P. (2023). Do growth mindset interventions impact students’ academic achievement? A systematic review and meta-analysis with recommendations for best practices. Psychological Bulletin, 149(3–4), 133–173. https://doi.org/10.1037/bul0000352
  • Scherer, R., Siddiq, F., & Sánchez Viveros, B. (2020). A meta-analysis of teaching and learning computer programming: Effective instructional approaches and conditions. Computers in Human Behavior, 109, 106349. https://doi.org/10.1016/j.chb.2020.106349
  • Sorva, J., Karavirta, V., & Malmi, L. (2013). A review of generic program visualization systems for introductory programming education. ACM Transactions on Computing Education, 13(4), Article 15, 1–64. https://doi.org/10.1145/2490822
  • Vygotsky, L. S. (1978). Mind in society: The development of higher psychological processes. Harvard University Press.
  • Wood, D., Bruner, J. S., & Ross, G. (1976). The role of tutoring in problem solving. Journal of Child Psychology and Psychiatry, 17(2), 89–100. https://doi.org/10.1111/j.1469-7610.1976.tb00381.x
  • Yeager, D. S., & Dweck, C. S. (2020). What can be learned from growth mindset controversies? American Psychologist, 75(9), 1269–1284. https://doi.org/10.1037/amp0000794

One response to “Teaching Performance Without Folklore: Why Simplified Models Need Somewhere to Go”

  1. […] Teaching Performance Without Folklore, I argued that simplified models work best when they are treated as starting points rather than […]

Leave a Reply

Discover more from Chris Goodings

Subscribe now to keep reading and get access to the full archive.

Continue reading