In
computing,
just-in-time
compilation (
JIT), also known as
dynamic translation, is a technique for improving
the runtime performance of a
computer
program. JIT builds upon two earlier ideas in run-time
environments:
bytecode compilation and
dynamic compilation. It converts
code at
runtime prior
to executing it natively, for example
bytecode into native machine code. The performance
improvement over interpreters originates from caching the results
of translating blocks of code, and not simply reevaluating each
line or operand each time it is met (see
Interpreted language). It also has
advantages over statically compiling the code at development time,
as it can recompile the code if this is found to be advantageous,
and may be able to enforce security guarantees. Thus JIT can
combine some of the advantages of interpretation and static
(
ahead-of-time) compilation.
Several modern runtime environments, such as
Microsoft's
.NET
Framework and most implementations of
Java, rely on JIT compilation
for high-speed code execution.
Overview
In a bytecode-compiled system,
Source
Code is translated to an intermediate representation known as
bytecode. Bytecode is not the machine code
for any particular computer, and may be portable among computer
architectures. The bytecode may then be interpreted by, or run on,
a
virtual machine. A just-in-time
compiler can be used as a way to speed up execution of bytecode. At
the time the bytecode is run, the just-in-time compiler will
compile some or all of it to native machine code for better
performance. This can be done per-file, per-function or even on any
arbitrary code fragment; the code can be compiled when it is about
to be executed (hence the name "just-in-time").
In contrast, a traditional
interpreted virtual machine
will simply interpret the bytecode, generally with much lower
performance. Some
interpreters even interpret source code,
without the step of first compiling to bytecode, with even worse
performance.
Statically compiled code or
native
code is compiled prior to deployment. A
dynamic
compilation environment is one in which the compiler can be
used during execution. For instance, most
Common Lisp systems have a
compile
function which can compile new functions created during the run.
This provides many of the advantages of JIT, but the programmer,
rather than the runtime, is in control of what parts of the code
are compiled. This can also compile dynamically generated code,
which can, in many scenarios, provide substantial performance
advantages over statically compiled code, as well as over most JIT
systems.
A common goal of using JIT techniques is to reach or surpass the
performance of static compilation, while maintaining the advantages
of bytecode interpretation: Much of the "heavy lifting" of parsing
the original source code and performing basic optimization is often
handled at compile time, prior to deployment: compilation from
bytecode to machine code is much faster than compiling from source.
The deployed bytecode is portable, unlike native code. Since the
runtime has control over the compilation, like interpreted
bytecode, it can run in a secure sandbox. Compilers from bytecode
to machine code are easier to write, because the portable bytecode
compiler has already done much of the work.
JIT code generally offers far better performance than interpreters.
In addition, it can in some or many cases offer better performance
than static compilation, as many optimizations are only feasible at
run-time:
- The compilation can be optimized to the targeted CPU and the
operating system model where the application runs. For example JIT
can choose SSE2 CPU instructions when it
detects that the CPU supports them. To obtain this level of
optimization specificity with a static compiler, one must either
compile a binary for each intended platform/architecture, or else
include multiple versions of portions of the code within a single
binary.
- The system is able to collect statistics about how the program
is actually running in the environment it is in, and it can
rearrange and recompile for optimum performance. However, some
static compilers can also take profile information as input.
- The system can do global code optimizations (e.g. inlining of
library functions) without losing the advantages of dynamic linking
and without the overheads inherent to static compilers and linkers.
Specifically, when doing global inline substitutions, a static
compiler must insert run-time checks and ensure that a virtual call
would occur if the actual class of the object overrides the inlined
method (however, this need not be the case for languages employing
a static type discipline).
- Although this is possible with statically compiled garbage
collected languages, a bytecode system can more easily rearrange
memory for better cache utilization..
Startup delay and optimizations
JIT typically causes a slight delay in initial execution of an
application, due to the time taken to load and compile the
bytecode. Sometimes this delay is called "startup time delay". In
general, the more optimization JIT performs, the better code it
will generate, but the initial delay will also increase. A JIT
compiler therefore has to make a trade-off between the compilation
time and the quality of the code it hopes to generate. However, it
seems that much of the startup time is sometimes due to IO-bound
operations rather than JIT compilation (for example, the
rt.jar class data file for the
Java Virtual Machine is 40 MB and the
JVM must seek a lot of data in this huge file).
One possible optimization, used by Sun's
HotSpot Java Virtual Machine, is to combine
interpretation and JIT compilation. The application code is
initially interpreted, but the JVM monitors which sequences of
bytecode are frequently executed and
translates them to machine code for direct execution on the
hardware. For bytecode which is executed only a few times, this
saves the compilation time and reduces the initial latency; for
frequently executed bytecode, JIT compilation is used to run at
high speed, after an initial phase of slow interpretation.
Additionally, since a program spends most time executing a minority
of its code, the saved compilation time is big. Finally, during the
initial code interpretation, execution statistics can be collected
before compilation, which helps to perform better
optimization.
The correct tradeoff can vary due to circumstances. For example,
Sun's Java Virtual Machine has two major modes—client and server.
In client mode, minimal compilation and optimization is performed,
to reduce startup time. In server mode, extensive compilation and
optimization is performed, to maximize performance once the
application is running by sacrificing startup time.
"
Native Image Generator" (Ngen.exe) by
Microsoft is another approach at reducing the
initial delay. Ngen pre-compiles (or
pre-jits) bytecode in
a
Common Intermediate
Language image into machine native code. As a result, no
runtime compilation is needed.
.NET
framework 2.0 shipped with
Visual
Studio 2005 runs Ngen.exe on all of the Microsoft library DLLs
right after the installation. Pre-jitting provides a way to improve
the startup time. However, the quality of code it generates might
not be as good as the one that is jitted, for many of the same
reasons why statically compiled code cannot be as good as JIT
compiled code in the extreme case.
There also exist Java implementations that combine an
AOT compiler with either a JIT compiler
(
Excelsior JET) or interpreter
(
GNU Compiler for Java.)
History
Dynamic translation was pioneered by the commercial
Smalltalk implementation
currently known as
VisualWorks, in the
early 1980s.
However it is also claimed that this
technique was used very much earlier, in 1974,
for an early spreadsheet implemented at
ICI, Mond division,
Cheshire
, UK
. Spreadsheet formulae were translated to
IBM/360 machine
code snippet when first
entered and, using
memoization
techniques, were then incorporated into the dynamically built and
reentrant runtime execution code, reaching speeds
in excess of equivalently compiled
FORTRAN.
Sun's
Self language
improved these techniques extensively and was at one point the
fastest Smalltalk system in the world; achieving up to half the
speed of optimized C but with a fully object-oriented
language.
Self was abandoned by Sun, but the research went into the Java
language, and currently it is used by most implementations of the
Java virtual machine, as
HotSpot builds on, and extensively uses,
this research base.
The HP project Dynamo was an experimental JIT compiler where the
bytecode format and the machine code format were of the same type;
the system turned HPA-8000 bytecode into HPA-8000 machine code.
Counterintuitively, this resulted in speed ups, in some cases of
30% since doing this permitted optimisations at the machine code
level, for example, inlining code for better cache usage and
optimizations of calls to dynamic libraries and many other run-time
optimizations which conventional compilers are not able to
attempt.
See also
Notes
- The Java HotSpot Performance Engine
Architecture
- History of Spreadsheets
- http://research.sun.com/jtech/pubs/97-pep.ps
- Ars Technica on HP's Dynamo
References
- L. Peter Deutsch and Allan M. Schiffman,
"Efficient Implementation of the Smalltalk-80 System",
11th Annual
Symposium on Principles of Programming Languages, Jan 1984,
pp. 297-302
- Free Online Dictionary of Computing entry
- John Aycock, "A brief history of just-in-time", ACM
Computing Surveys, 35,2, 2003, pp. 97-113
- Matthew Arnold, Stephen Fink, David Grove, Michael Hind, and
Peter F. Sweeney, "A Survey of Adaptive Optimization in Virtual
Machines", Proceedings of the IEEE, 92(2), February 2005, Pages
449-466.
External links
- GNU lightning — A library that generates
assembly language code at run-time
- — A library by Rhys Weatherley, Klaus Treichel, Aleksey
Demakov, and Kirill Kononenko for development of Just-In-Time
compilers in Virtual Machine implementations, Dynamic programming
languages and Scripting languages.
- SoftWire — A library by Nicolas Capens that generates
assembly language code at run-time
- AsmJit — Complete x86/x64 jit assembler library for
C++ language by Petr Kobalíček
- Xbyak — A x86/x64 JIT assembler for C++
language by Herumi
- [31350] An earlier implementation of JIT in 1974 at
ICI for spreadsheets