Pitfalls of Benchmarking on Modern Systems · Stefan-Marr.de
Stefan Marr
Full Professor (Universitätsprofessor)
I head the<br>Institute for System Software<br>at the Johannes Kepler University Linz. My work focuses on programming language<br>implementation techniques, compilation, complex concurrent systems, and tooling.
I am personally interested in making interpreters faster, detecting and<br>preventing concurrency bugs at run time, and use modern language<br>runtime systems to provide programmers with better tooling.
For contact details, please see<br>my staff page.
© 2026 stefan-marr.de
Modern computer systems are quite fascinating.<br>They are highly complex, run a lot of software, and their performance characteristics are hard or impossible to predict.
Earlier this year, I gave a lecture on benchmarking,<br>which for lack of generic and always-applicable advice,<br>I started with a brainstorming session<br>about its pitfalls.<br>Since systems have become so complex that we rarely know what happens exactly.<br>We are prone to simply guess what’s going on.<br>Or rather, we should hypothesize and verify our hypothesis.<br>Still, there might be many more causes for what we see than we can initially think of.
To illustrate this, I’ll use some fictitious benchmark results,<br>which are fairly close to what one would see in reality,<br>and there are usually many possible explanations<br>for the observed behavior.<br>This often means, only once we know what caused a specific artifact,<br>we can make progress with understanding what we set out to understand in the first place.
The Scenario: A “Reasonably Deterministic” Workload
Let’s start with our hypothetical/fictitious benchmark.<br>One rather strong assumption we are going to make is that our benchmark<br>is “reasonably deterministic”. Thus, when we run it multiple times,<br>it pretty much does the same thing.<br>To make this more concrete, let’s assume our benchmark does what one of the very first business applications did: payroll. We have a program that generates PDFs with the monthly payslips.<br>We run our benchmark on a Linux from 2026 and since our hypothetical benchmark<br>is written in Java, we use the HotSpot JVM and JDK 26.
A First Run
We let this benchmark run for 8 iterations within the same JVM process<br>and visualize all our data points with a scatter plot. On the y-axis we show run time,<br>which means lower is better.<br>On the x-axis, we have the number of the specific iteration.
Figure 1:<br>A first run of our benchmark.<br>The x-axis shows the iterations, and the y-axis the run time.<br>Thus, lower is better. We see that each subsequent iteration<br>got faster until iteration 4, and then we stayed at the same<br>performance level.
On the plot we can see that iterations 2, 3, 4 are each a bit faster than<br>the previous one, and then we stay at the same level of performance.
Given that we use a language implementation with just-in-time (JIT) compilation,<br>this is what we would roughly expect. The JIT compiler manages to optimize the<br>code we are executing step by step, and we see that it improves performance.
A Second Run, Same Benchmark, Nothing Changed
We ran the exact same setup a second time,<br>getting 8 new data points.<br>But as we can see in Figure 2,<br>the benchmark is faster in iteration 3 and following!<br>What happened here?
Figure 2:<br>A second run of the same benchmark, with the same setup.<br>Surprisingly, it is quite a bit faster than on the first run!
This is where we now start hypothesizing.
What could it be?<br>It could be that the operating system decided<br>to give it different physical memory,<br>or run it on a different kind of core (many CPUs these days have 2 or more different types of cores with different performance).<br>Perhaps, we ran out of thermal budget and the CPU ran at a lower clock speed to<br>avoid overheating?<br>Or, since we are on a JVM,<br>perhaps the compiler saw slightly different information for the types/behavior<br>seen in the first and second iterations, and thus, made slightly different optimization decisions?<br>This is possible because compilation happens on a background thread, and thus,<br>even when the benchmark is deterministic, the used profiling information is to some degree racy.
It could also be something entirely different however, which means, we do not really know.
For designing your benchmark methodology,<br>you’ll need to decide on how to take these variables into account.<br>Sometimes, you may simply collect data from more runs, ideally many runs,<br>so that you can characterize this behavior as part of the<br>performance distribution one might likely observe in practice.<br>In other cases, this might be too naive, and you need to carefully control<br>for specific variables to get useful data from your experiments.<br>This can include pinning threads to specific cores, fixing CPU frequencies,<br>disabling address space layout randomization, etc. Each approach comes with different<br>tradeoffs.
A Third Run, And More Data
We ran the same benchmark, with the very same setup, a third time.<br>And, we actually had a...