[ Friday, 7 November 2008, tompelka ]
In this article I’m going to go through some details about what is going on under the hood when you run a Python program, as well how those details have changed over the years in different python’s implementations ranging from original CPython (Python implementation in C) all the way to the newest implementations like PyPy.
First of all start with some basic background explanation about how CPython works, an of overview how python programs run and the operation of the python virtual machine. Then I’ll touch on bytecode and disassembler and an overview of difficulties in the design decisions that were made for CPython. Afterwards, I’ll touch how other implementations different from CPython. I’ll start with Jython, IronPython, then JIT (Just In Time) compilation and the psyco module. I’ll briefly review Shed skin, which is a Python-to-C++ compiler and also touch on Parrot virtual machine. Finally, I’ll talk about stackless Python and after all that will be PyPy that incorporate all the best ideas from all another implementations of python’s VM’s.
As you probably already know, there is a growing number of Python distribution to choose from. Some major implementations includes not only original implementation called CPython which is wide spread in mature but also younger implementations like Jython and IronPyton and perhaps the newest implementation is PyPy. PyPy is specially interesting because incorporates many great ideas that have come up over the years in other Python implementations. PyPy version 1.1 just came out in September 2008 (1.0 in March 2007) and given this milestone, it seems like a good time to take look back at the history major Python implementations, to appreciate how they evaluate and build on each others ideas and also how they will continue.
CPython is a basic
So lets go over some basics about how python program runs. Don’t panic, I like to be clear. If you are already aware how Python runs code, than you can skip next few lines. Let’s start with CPython. As I said earlier, CPython is the reference implementation of Python language, you can get it from www.python.org. First release of it was in 1991 and current version is 2.6. It is named CPython because interpreter itself is written in pure C by Guido van Rossum. So when you running a python program, you are actually runs a C program which interprets your python program. Your python source code is first compiled into intermediate form called bytecode and then that code is then interpreted by what’s called Python virtual machine. If you are familiar with Java that you can see the similarity with Java byte code and Java virtual machine. Is not exactly the same byte code, but very similar. Why are bytecodes used? Well using bytecode speeds up execution, since bytecode is more compact and easier to interpret and manipulate than the original Python source code. But bytecode is not to be confused with machine code, like machine code for x86 processors. Bytecode is a higher level code that is specific to Python VM. So now we have a bytecode that is feeding the virtual machine. And basically a VM is a big loop. It gets the bytecode that has been sent to it and examines the bytecode to determine which C function has to be executed to implement the instruction for the bytecode. Each bytecode represents the operation on internal Python virtual machine data structures at the C code level. Pretty abstract isn’t it? You can watch what the VM is doing with python disassembler module
Here is a short example, first I define a new simple function:
>>> def double(x): ... return x*x
And this is the result:
>>> import dis >>> dis.dis(double) 2 0 LOAD_FAST 0 (x) 3 LOAD_FAST 0 (x) 6 BINARY_MULTIPLY 7 RETURN_VALUE
So we can say that CPython is a stack based language. It looks very similar to CPU language for processors. Enough with bytecode. Let’s summarize bytecode:
- Python bytecode is instructions that manipulates objects not values. No LOADs, PUTs, JUMPs etc.
- Python is a dynamic language, so no C equivalent for some bytecode instructions like build class (
class) or make function (
def). These two instructions tell the interpreter make these objects on the fly while the program is running.
Next, although CPython supports all the flexibility of the Python language, the internal design is not as flexible as it could be. In the design phase, a few decisions had to be made that are fixed to all version of CPython code. For example, garbage collection (is not easy to implement new memory management algorithms), threading model (CPython use what is called Global Interpreter Lock – GIL, that makes internal data structures coherent when multiple threads were running simultaneously), etc.
Note: GIL is the reason why CPython can’t use the potential of todays multi-core processors for multi-threaded applications.
Rewriting this C code base is, well, impossible because the code is old, huge and tricky to maintain. Let’s ask ourselves a question: “Can we ever create a new distribution to address the weaknesses with CPython?”.
Well the short answer is: “Yes”. We have tried and many have succeeded. We’ll talk about the distributions next.
Jython is a python implementation that allows you to run python programs within a Java environment. It was originally created by Jim Hugunin in late 1997. He explored that Java could be as fast as C for simple numerical benchmarks, and he also discovered that it was easy to translate Python to Java by hand.
What exactly is Jython? Jython is a set of Java classes that allows Python bytecode to run on a Java Virtual Machine. Using Java Virtual Machine for Python has many advantages.
- Since Python and Java are using the same virtual machine, is very easy to import and use Java classes in Python.
- Using the JVM allows Jython to utilize all the work that has gone into improving and tuning Java VM. For example, Jython can use java’s garbage collector, JVM has existing threading library, no GIL and multi-core processors restriction.
- Is not necessary to reimplement processes like exception handling, libraries and other things that JVM provides.
- You can also use HotSpot optimizations.
So, Jython is more natural for Python that CPython, because Java is a fully object oriented language, whereas C is not. But there are some disadvantages to using Jython. Jython runs slightly slower than CPython. At this time, it is recommended to use version 2.2.1 (even if 2.5a3 is available), 2.2.1 is approximately equal to CPython 2.2. Unfortunately, Java is not directly fully compatible with C based extension modules in CPython.
Most people argue that because Java was designed for non dynamic language, the dynamic language of Python does not work well in it. This is only slightly true, obviously Jython works, but Sun Microsystems also says that they are working to extend JVM to provide stronger support for dynamic languages. In fact, in the last approximately two years they have included the new JSR 292 (adding new bytecode
invokedynamic), which deals with Dynamic Language Support to the JVM. For more information see this. One great example of Java’s dynamic language support is Groovy.
Lets take a short look to Java HotSpot VM options which speeds up Java execution. It is Java’s combination of JIT (Just In Time) compilation and adaptive optimization. These two techniques are very useful for dynamic languages like Python.
How it works: by interpreting the bytecode, the Java VM watches for “hotspots”; that is frequently executed sections of bytecode. These hot segments are compiled “just in time” by the compiler into machine code, where the program is running. This code is cached, so next time it isn’t requiered to recompile it. Could we used this technique also for Python (Jython)? Yes, we can with the module called Psyco.
In 2001, two years after Java HotSpot technology came about, a team lead by Armin Rigo started the project called Psyco. It is an open-source project with a goal to add JIT into CPython. What it does is emit machine code on the fly instead of interpreting the Python program step-by-step. Once the machine code is generated, the code is cached and run dynamically rather than as interpreted bytecode. The benefit here is that the program runs faster – between 2x and 100x depends on what you doing. The typical acceleration is 4x. The 100x increase is seen more in algorithmic applications, like tiny loops. The only disadvantag is large memory usage and that Psyco runs only on i386 compatible processors.
If you are interested look at the Psyco homepage.
If you are interested in Jython please visit the homepage.
Jim Hugunin, yes the same person who created Jython, then moved on to Microsoft. There, he is using his experiences with Jython to create another Python distribution called IronPython. IronPython allows Python code to run on Microsoft VM, which is a CLR (Common Language Runtime). It is similar to JVM, but not exactly the same. It provides common services for all languages that it hosts. For example memory management, exception handling, threading support, security etc.
Also, Microsoft decided to add special features for dynamic languages called dynamic method class.
If you are interested for IronPython you can check the homepage.
As I mentioned above, Shed Shed is a Python-to-C++ compiler. But, it’s hard to deal with the dynamic runtime information after the program is compiled. For example, Python doesn’t declare variables, C++ does. So Shed Skin uses a type inferencing algorithm to guess variables types. Other disadvantages are that Python can’t retype variables after compilation, and not all Python features are supported.
Although it is an experimental project, it shows that it is possible to run Python programs more than 2x – 40x faster over Psycho and more than 2x – 220x over CPython. Its also interesting know how much “just in time” optimization is possible.
If you are interested go to the homepage.
Homepage is located here.
This python distribution is adjusted to handle massive concurrency; it can run thousands of threads simultaneously. This is very useful for simulations and games as an example. Stackless is used extensively in the implementation of the EVE Online multiplayer game to provide for concurrency, Civilization IV, as well as in IronPort‘s mail platform. Second Life is also beginning to use it.
If you are interested check homepage.
Wouldn’t it be great if there was a single distribution that can provide everything of all the mentioned distributions above, and more? Well, we need an interpreter which can run on every mentioned interpreter and is easier to maintain than interpreters written in C. You can probably guess that I’m hinting towards what PyPy does today.
PyPy is an open-source project which was started back in 2003 by Armin Rigo (creator of Psyco) and Christian Tismer (creator of stackless Python). PyPy is constructed from various components:
- Python interpreter – written in Python.
- Set of tools also written in Python. This set allows using different VM’s
One of goals of PyPy if very fast execution, JIT is also included. It also fairly compatible with CPython, up to version 2.4.1. It still isn’t mature enough yet for daily use, even thought it passes around 98% of CPythons core language regression tests.
In my opinion, it is a very interesting fact that interpreter is written in same language that interprets. This sounds pretty weird, doesn’t it? You can run Python interpreter on Python interpreter But it is an approved technique for large projects. PyPy can be run on all python interpreters, that I mentioned, but its very slooow.
I have to mention a few other interesting features that PyPy have. Except JIT and stackless features (core routines, see stackless homepage for more info), PyPy provides sandboxing. Sandboxing features are very useful to increase security of applications. The application can run fully virtualized. Or you can define proxy objects, and create something like an internal application firewall. You can also use the PyPy translators, that can translate bytecode to C, Java or Prolog for example:)
If you are interesting in PyPy, head over to the homepage for more information. They have great documentation I must say.
Proof-read by Phillip Smith