7

Compiled code such as C consumes little memory.

Interpreted code such as Python consumes more memory, which is understandable.

With JIT, a program is (selectively) compiled into machine code at run time. So shouldn't the memory consumption of a JIT'ed program be somewhere between that of a compiled and an interpreted program?

Instead a JIT'ed program (such as PyPy) consume several times more memory than the equivalent interpreted program (such as Python). Why?

Continuation
  • 12,722
  • 20
  • 82
  • 106
  • Everyone talks about the JIT being the cause of the increased memory usage in Pypy, but that isn't the whole story. While some of Pypy's memory structures are more compact (lists of all ints for instance), Pypy has a variety of garbage collectors that can be used with it and they will definitely affect the amount of memory in use. The current default garbage collector in Pypy does not do reference counting in the interest of speed. Due to this, objects can remain in memory longer than they would in CPython and thereby programs can have a bigger memory footprint in Pypy. – Justin Peel Jan 02 '12 at 05:26

2 Answers2

8

Tracing JIT compilers take quite a bit more memory due to the fact that they need to keep not only the bytecode for the VM, but also the directly executable machine code as well. this is only half the story however.

Most JIT's will also keep a lot of meta data about the bytecode (and even the machine code) to allow them to determine what needs to be JIT'ed and what can be left alone. Tracing JIT's (such as LuaJIT) also create trace snapshots which are used to fine tune code at run time, performing things like loop unrolling or branch reordering.

Some also keep caches of commonly used code segments or fast lookup buffers to speed up creation of JIT'ed code (LuaJIT does this via DynAsm, it can actually help reduce memory usage when done correctly, as is the case with dynasm).

The memory usage greatly depends on the JIT engine employed and the nature of the language it compiles (strongly vs weakly-typed). some JIT's employ advanced techniques such as SSA based register allocators and variable livelyness analysis, these sort of optimizations helps consume memory as well, along with the more common things like loop variable hoisting.

Necrolis
  • 25,836
  • 3
  • 63
  • 101
  • 1
    Also, JIT is real-time compilation, so it must trade off optimization for speed. An offline compiler can afford to spend 5 seconds optimizing a function. A JIT not so much. – Raymond Chen Dec 30 '11 at 02:28
  • @RaymondChen: totally depends on the JIT, if its written correctly, you don't need to trade off *that* much optimization, its only really the expensive analysis techniques that are forgone. – Necrolis Dec 30 '11 at 02:36
  • Also, JITs are usually *specialising* compilers. The whole point of doing compilation "just in time" is that the compiler can wait and see what is being used at runtime and optimise specifically for that. Which means (depending on the JIT) that there may be several different versions of the machine code for a given section of bytecode in memory at any one time, along with the original byte code in case a new situation comes up that doesn't fit any of the compiled versions. – Ben Dec 30 '11 at 03:01
  • Add the size of JIT itself. Some (like Mono) are tolerably small, but some (like HotSpot) are huge and bulky. – SK-logic Dec 30 '11 at 08:24
5

Be careful about what kind of memory usage you're talking about.

Code compiled to C uses comparatively little memory for the compiled machine code itself.

I would expect Python bytecode for a given algorithm to actually be smaller than the compiled C code for a similar algorithm, because Python bytecode operations are much higher level so there's often fewer of them to get a given thing done. But a Python program will also have the compiled code of the Python interpreter in memory, which is quite a large and complex program in itself. Plus a typical Python program will have much more of the standard library in memory than a typical C program (and a C program can strip out all the functions it doesn't actually use if it's statically linked, and if it's dynamically linked then it shares the compiled code with any other process in memory that uses it).

PyPy then has on top of this the machine code of the JIT compiler, as well as the machine code generated from the Python bytecode (which doesn't go away, it has to be kept around as well). So your intuition (that a JITed system "should" consume memory somewhere between that of a compiled language and a fully interpreted language) isn't correct anyway.

But on top of all of those you've got the actual memory used by the data structures the program operates on. This varies immensely, and has little to do with whether the program is compiled ahead of time, or interpreted, or interpreted-and-JITed. Some compiler optimisations will reduce memory usage (whether they're applied ahead of time or just in time), but many actually trade off memory usage to gain speed. For programs that manipulate any serious amount of data it will completely dwarf the memory used by the code itself, anyway.

When you say:

Instead a JIT'ed program (such as PyPy) consume several times more memory than the equivalent interpreted program (such as Python). Why?

What programs are you thinking of? If you've actually done any comparisons, I'm guessing from your question that they would be between PyPy and CPython. I know many of PyPy's data structures are actually smaller than CPython's, but again, that has nothing to do with the JIT.

If the dominant memory usage of a program is the code itself, then a JIT compiler adds huge memory overhead (for the compiler itself, and the compiled code), and can't do very much at all to "win back" memory usage through optimisation. If the dominant memory usage is program data structures, then I wouldn't be at all surprised to find PyPy using significantly less memory than CPython, whether or not the JIT was enabled.


There's not really a straightforward answer to your "Why?" because the statements in your question are not straightforwardly true. Which system uses more memory depends on many factors; the presence or absence of a JIT compiler is one factor, but it isn't always significant.

Ben
  • 68,572
  • 20
  • 126
  • 174
  • I disagree with your sentence "If the dominant memory usage of a program is the code itself, then a JIT compiler adds huge memory overhead..." - this is false for a well-designed hotspot JIT compiler. I also disagree with your sentence "If the dominant memory usage is program data structures, then [...] PyPy using significantly less memory than CPython" - PyPy is NOT that good. –  Jan 09 '12 at 17:40
  • @Atom A JIT compiler translates code to code at runtime. A specialising JIT compiler *must* also keep the original code around (a non-specialising JIT compiler isn't terribly interesting, as it could be implemented as an ahead-of-time compiler, gaining efficiency of not having to run it at runtime, and the ability to spend more time on optimisations). Therefore it must add *at minimum* O(n) space overhead, where n is the amount of code compiled by the JIT compiler. Plus the memory of the JIT itself... – Ben Jan 10 '12 at 01:21
  • @Atom It's true that usually the JIT doesn't compile most of the program, so I was possibly overstating the case. But how much of the code of a program is JIT-compiled is always going to be dependent on the program. It is certainly the case that for any given JIT system there are programs that will pay a significant amount of memory overhead compared to the same program executing in a non-JITed environment. – Ben Jan 10 '12 at 01:28
  • @Atom Re PyPy memory usage: PyPy's implementation of class instances uses about the same memory usage as if you'd used `__slots__` everywhere in CPython. See http://morepypy.blogspot.com.au/2010/11/efficiently-implementing-python-objects.html. This is a VERY significant memory saving in some cases. They have some other space-wins as well. I have no idea how overall memory usage stacks up against CPython for whole real programs, but certainly PyPy is "that good" for some parts of the picture at least. Hence "I wouldn't be surprised...", not "PyPy definitely uses less memory". – Ben Jan 10 '12 at 01:32