A few years ago we modified the Mono code generation engine
from being merely a Just-in-Time compiler for managed code to
be also a batch compiler that could be used to batch compile
code in advance.
We call this batch compilation process "Ahead of Time
Compilation" or AOT.
There are many reasons for supporting AOT compilation:
reduced startup time, reduced overall memory usage and the
possibility of running better optimized code.
This batch compilation is typically done by invoking the
mono command with the --aot flag on a managed assembly, like
this:
$ mono --aot hello.exe
$ ls -l hello.exe.*
-rwxr-xr-x 1 miguel users 3584 2006-08-15 12:38 hello.exe
-rwxr-xr-x 1 miguel users 10769 2006-08-17 00:05 hello.exe.so
The .exe file in the above listing contains the managed
code in CIL format, while the hello.exe.so is an ELF file that
contains the pre-compiled code.
Today, the .so file only contains the pre-compiled
code but does not contain any of the additional metadata that
is present on the .exe file. To run the application it is
necessary to have the original assembly (.exe or .dll) around
as it contains information that is not replicated on the
executable.
To execute the code, you must still invoke Mono with the
original parameters, for instance:
$ mono hello.exe
The runtime will take care of probing whether there is a
native pre-compiled image, and if the image is valid, it will
use the pre-compiled code from the shared object instead of
JITing the code as it goes.
As I mentioned before, there were an array of motivations
for implementing AOT in Mono. Initially we hoped to reduced
application startup time as the JIT had less work to do. In
practice, the Mono JIT was fast enough that the JIT time was
never much of an issue, even with the last batch of
optimizations that were turned on in Mono 1.1.16, the startup
speed is not even noticeable. In addition, there is a slight
memory gain by not executing code in the JIT in the first
place, so there was a small memory improvement as well.
Heavier optimizations that exist today, and heavier
optimizations that we are developing will make the use of AOT
more important, as those code generation optimizations are
slower to have enabled by default on the Just-in-Time
compilation stage.
This is what the original design of the AOT file format was
designed to cope with: startup time.
In the last few months Zoltan has been working on adapting
AOT for another task: overall memory reduction.
As developers start to run a handful of Mono applications
on a desktop, we want to minimize the duplicated code in
memory. To do this, we had to address a number of problems
with the original AOT design.
The original AOT design had poor multi-process page sharing
capabilities, as the JIT had to fix-up the mapped AOT code in
memory. The native code was shared across multiple processes
by using mmap() to map the native code into each
process, but as the fix-ups were applied to the mapped code,
the numbers of pages that could be shared across processes
decreased.
The new design by Zoltan for the AOT generated code is very
similar to the ELF shared libraries file format. It uses a
couple of tables for data and code (Global Offset Table a
slightly modified Program Linkage Table tuned for Mono
compilation) which are the only pieces that are modified.
$ mono --aot /mono/lib/mono/1.0/mscorlib.dll
Mono Ahead of Time compiler - compiling assembly /mono/lib/mono/1.0/mscorlib.dll
Code: 1632832 Info: 105844 Ex Info: 46601 Class Info: 39399 PLT: 4127 GOT: 55756
Executing the native assembler: as /tmp/mono_aot_jiqD57 -o /tmp/mono_aot_jiqD57.o
Executing the native linker: ld -shared -o /mono/lib/mono/1.0/mscorlib.dll.so
/tmp/mono_aot_jiqD57.o
Compiled 11050 out of 11051 methods (99%)
1 methods contain absolute addresses (0%)
0 methods contain wrapper references (0%)
0 methods contain lmf pointers (0%)
0 methods have other problems (0%)
Methods without GOT slots: 7322 (66%)
Direct calls: 11350 (53%)
GOT slot distribution:
methodconst: 25
switch: 167
class: 2176
field: 165
vtable: 3241
sflda: 2356
ldstr: 3003
ldtoken: 14
type_from_handle: 546
iid: 414
adjusted_iid: 1831
AOT RESULT 0
By reducing the pieces that have to be patched to a small
set of pages, the rest of the code can be shared across
multiple Mono processes.
Now, with the same set of optimization flags, AOT code is
slightly slower than JITed code because it has to be position
independent and has to go through a few extra indirections
that JIT code does not have to do (to maximize the sharing
across applications).
The big news is that the new file format design has finally
reached the point where using AOT code on day to day
operations is faster than using JIT code. Building our core
class libraries with our C# compiler with AOT code now takes
less time than using the JIT-tuned version.
The mscorlib compilation consists of 262,261 lines of C#
source code, the results are:
JIT time: 4.34 seconds
AOT time: 4.21 seconds
Today AOT is only available on the x86 and x86-64 ports of
Mono, this seems to be good enough and where most of the
desktop deployments will be.
Now, one common question we get is: if we already generate
native code, how come you still need Mono to run your
applications; or the variant of the question: can I produce
statically generated executables that only contain native
code?
It is currently not possible to generate merely a native
code file because Mono still needs to extract the metadata
from the original assembly file (the .dll or the .exe file),
information that we do not encode in the AOT file format. In
addition the generated image still requires the Mono runtime
(for GC, Just-in-Time compilation services, reflection and a
handful of extra features).
Mono does support a special mode that turns an assembly
into a static binary, but it does not use native code, it
still uses the JIT engine. This is done using
the mkbundle
command.
It is still possible to turn off the use of AOT files in a
per-run basis, this can be done by passing the -O=-aot
command line option to the runtime. In the upcoming version
of Mono, all of our wrapper scripts will also support the
MONO_OPTIONS variable to control this.
We are hoping to ship a script that will AOT all the
libraries and executables that ship as part of Mono, or to
disable all the AOT executables on demand in a future release
of Mono.
Don asked me at the Lang.NET Symposium how we handled
generics, and I have no idea.