Just-in-time Compilation and Multi-platform Support
Learning Objectives
- You know of just-in-time compilation and multi-platform support.
So far, we’ve looked into the compilation process, linking, loading, and the runtime environment. Contemporary programming languages also include features like just-in-time (JIT) compilation and multi-platform support, which muddle the boundaries between traditional compilation and execution. Here, we briefly visit these.
Just-in-time Compilation
Just-in-time compilation is an approach where code is compiled into machine code during runtime rather than ahead-of-time. This allows making informed decisions on what parts of the code need to be optimized based on actual usage. By delaying compilation until execution, a program can adapt to the varying demands of the environment, offering a balance between speed and flexibility that traditional ahead-of-time (AOT) compilers often struggle to achieve.
JIT systems often use an intermediate representation such as byte code that the code is initially compiled into. This intermediate representation is then interpreted by the runtime, and translated into machine code as needed, allowing the compiler to apply optimizations based on runtime data.
A key piece of JIT is profiling and runtime analysis. The compiler collects data on code execution as the program runs, identifying “hot spots” or frequently executed segments. This data provides a picture of how the software operates under typical conditions, enabling the compiler to focus its optimization efforts on the most performance-critical areas.
JIT can further be enhanced by using tiered compilation. In tiered systems, code is initially compiled using a fast, less-optimized method to ensure quick startup times. As the application continues to run and specific routines are executed repeatedly, the compiler re-evaluates and applies more aggressive optimizations. This multi-level strategy allows for a smooth balance between initial performance and long-term efficiency.
However, in certain scenarios, deeply nested or complex code structures can lead to situations where frequently jitted code segments are continuously torn down and rebuilt. This behavior not only undermines the benefits of JIT but can also introduce instability.
As an example of problems that JIT can introduce, see the thesis measuring and improving the performance of a latency-sensitive Java real-time system. The thesis highlights a scenario, where JIT tiered compilation optimizations lead to periodically observable performance degradation.
Multi-platform Support
Compilers are increasingly expected to support a range of target platforms. The classic example of multi-platform support is Java, which compiles source code into bytecode that can be executed on any system with a Java Virtual Machine (JVM).
At the core, multi-platform support involves translating code to conform to platform-specific requirements, such as system calls, memory models, and file system structures. Intermediate representations, as discussed earlier, play a key role in this process by providing a common ground for translating code into platform-specific executables, reducing the overhead associated with maintaining separate codebases for different environments.
The most common approaches for multi-platform support include cross-compilation, where code is compiled on one system for execution on another, and platform-specific virtual machines or generic bytecode, which can be interpreted on any device that has the appropriate runtime environment.
These approaches are in contrast to providing native multi-platform support, which demands a deep understanding of the target platforms and often requires significant effort to maintain.
Recently, there have increasingly been solutions that provide means for cross-compilation and multi-platform support. For instance, the LLVM compiler infrastructure offers a versatile platform for generating machine code for a wide range of architectures. Similarly, WebAssembly (Wasm) offers a standardized way to run code at near-native speeds in secure sandboxed environments.
As an example, Rust compiles to LLVM intermediate representation, which can then be translated to machine code for a variety of platforms. LLVM also supports WebAssembly as a target.