Mixing Execution Modes to Balance Trade-offs in Modern Runtimes

Christoph Pichler


Abstract

Modern runtimes face fundamental trade-offs between peak performance, warm-up performance, and application size: AOT compilation enables fast warm-up by pre-compiling code. JIT compilation dynamically optimizes hot code paths to achieve high peak performance, while cold paths are executed in a less-optimized mode or even interpreted. However, AOT compilation typically yields large binaries, while profiling and optimizing at run time introduce warm-up costs for JIT compilation. These trade-offs can be mitigated by hybrid execution models, which combine multiple execution modes. While execution modes are specific to the language implementation, some systems enable mixed-mode execution, for example, via native extensions. However, this approach requires developers to explicitly manage mode selection by strategically choosing language implementations for different code segments.

This work, in contrast, shows that automatically mixing execution modes unlocks missed optimization possibilities. On the one hand, it is demonstrated how to reduce binary executable size: Rarely executed code is not compiled to highly optimized machine code, but can instead be kept as compact bytecode, executed by a lightweight interpreter. On the other hand, this work shows that mixing execution modes can achieve peak and warm-up performance benefits.

Evaluations on DaCapo, Renaissance, lxml and ujson benchmarks show that it can pay off to automatically mix execution modes. While static heuristics are a feasible choice and can be applied quickly, dynamic heuristics achieve better results in most cases. When heuristics are developed and tuned, it is important to consider code that is by design bound to run in a certain mode. Also, experiments indicate that the characteristic properties of initialization code affect performance and code size in a different way than other code does.

This work has been implemented on top of GraalVM, a high-performance, production-grade and multi-language runtime that is also widely used in industry. This demonstrates the feasibility of this work in state-of-the-art systems and shows its impact on an already highly optimized environment.

Kurzfassung

Moderne Laufzeitumgebungen stehen vor grundlegenden Kompromissen zwischen Höchstleistung, Dauer der Aufwärmphase und Größe des Programms: Ahead-of-Time-Kompilierung ermöglicht einen schnellen Start durch das vorzeitige Kompilieren des Codes. Just-in-Time-Kompilierung optimiert häufig genutzte Codepfade zur Laufzeit, um eine gute Höchstleistung zu erzielen. Selten genutzte Pfade werden hingegen weniger optimiert ausgeführt oder sogar interpretiert. Allerdings führt eine vorzeitige Kompilierung in der Regel zu großen Binärdateien, während die Analyse und die Optimierung zur Laufzeit Aufwärmkosten für die JIT-Kompilierung verursachen. Diese Kompromisse lassen sich durch hybride Ausführungsmodelle, die mehrere Ausführungsarten kombinieren, abschwächen. Da die Implementierung einer Programmiersprache eines Codes den Ausführungsmodus bestimmt, ermöglichen manche Systeme eine Ausführung mit gemischten Modi, etwa über Native Extensions. Dieser Ansatz erfordert jedoch, dass Entwickler:innen die Implementierungen der Programmiersprachen für verschiedene Codesegmente strategisch explizit auswählen, um die entsprechenden Ausführungsmodi festzulegen.

Diese Arbeit zeigt hingegen, dass das automatisierte Kombinieren verschiedener Ausführungsmodi neues Verbesserungspotential ermöglicht. Einerseits wird gezeigt, wie sich die Größe der kompilierten Programmdateien reduzieren lässt: Selten ausgeführter Code wird nicht zu hochoptimiertem Maschinencode kompiliert, sondern kann stattdessen als kompakter Bytecode behalten und von einem einfachen Interpreter ausgeführt werden. Andererseits zeigt diese Arbeit, dass Programme durch das Kombinieren verschiedener Ausführungsmodi Verbesserungen bezüglich Höchstleistung und Aufwärmphasen erzielen können.

Auswertungen anhand von DaCapo-, Renaissance-, lxml- und ujson- Benchmarks zeigen, dass es sich lohnen kann, Ausführungsmodi automatisiert zu kombinieren. Während statische Heuristiken eine praktische Möglichkeit darstellen und sich schnell anwenden lassen, erzielen dynamische Heuristiken in der Regel bessere Ergebnisse. Beim Entwickeln und Optimieren von Heuristiken ist es wichtig, Code zu berücksichtigen, der zwangsläufig in einem bestimmten Modus ausgeführt werden muss. Außerdem zeigen Experimente, dass die charakteristischen Eigenschaften von Initialisierungscode die Leistung und die Codegröße anders als durchschnittlicher Code beeinflussen.

Die Implementierungen dieser Arbeit basieren auf GraalVM, einer leistungsstarken und mehrsprachigen Laufzeitumgebung, die auch in der Industrie weit verbreitet ist. Dies zeigt die Umsetzbarkeit dieser Arbeit in modernen System des aktuellen Stands, und verdeutlicht, dass trotz bereits hochoptimierter Systeme mit den Ansätzen dieser Arbeit noch Verbesserungen erreicht werden können.


PhD thesis, Johannes Kepler University Linz, September 2026

Download as PDF