Python is a reasonably fast language, but it’s not as fast as compiled programs. That’s because CPython, the standard implementation, is interpreted. To be more precise, your Python code is compiled into byte code that is then interpreted. That’s good for learning, as you can run code in the Python REPL and see results immediately rather than having to compile and run. But because Python programs aren’t that fast, developers have created several Python compilers over the years, including IronPython and Jython.
Fast performance isn’t the only reason for compiling; possibly the biggest disadvantage of scripting languages such as Python is that you implicitly provide your source code to customers.
I wanted to compare a few Python compilers on the same platform, especially those that support Python 3.x. In the end, I chose four, all running on Ubuntu Linux: Nuitka, PyPy, Cython and cx_Freeze. (Originally I targeted five, but Pythran didn’t like the benchmark programs I used, so it didn’t make the cut.)
Comparing Python Compilers
Somebody has already done the work of creating a Python benchmark. I opted for PyStone, a translation of a C program by Guido van Rossum, the creator of Python (the C program was itself a translation of an Ada program). I found a converted version by developer Christopher Arndt on GitHub that was capable of testing Python 3. To give a sense of perspective, here’s CPython (i.e., standard Python) performance with Pystone:
Python 2.7.15Rc1 2 : 272,647 pystones/second. Python 3.6.5 : 175,817
As you can see, there’s quite a big difference between Python 2 and 3 (the more Pystones per second, the better). In the following breakdowns, all Python compilers were benchmarked against Python 3.
Although you can follow the instructions on the download page, the following on Ubuntu worked fine for me:
sudo apt install Nuitka
sudo apt install clang
By default, Nuitka uses gcc, but a parameter lets you use clang, so I tested it with both. The clang compiler is part of the llvm family, and is intended as a modern replacement for gcc. Compiling pystone.py with gcc was as simple as this (first line), or with clang (second line), and with link time optimization for gcc (third line):
nuitka pystone.py nuitka pystone.py --clang nuitka pystone.py --lto
After compiling, which took about 10 seconds, I ran the pystone.exe from the terminal with:
I did 500,000 passes:
Size Execution pystones/sec 1. 223.176 Kb 597,000 2. 195,424 Kb 610,000 3. 194.2 kb 600,000
These were the averages over 5 runs. I’d closed down as many processes as I could, but do take the timings with a bit of salt because there was a +/- 5% on timing values.
Guido van Rossum once said: “If you want your code to run faster, you should probably just use PyPy.” I downloaded the portable binaries into a folder, and, in the bin folder under that, copied pystone.py. Then I ran it like this:
The result was a stunning 1,776,001 pystones per second, almost three times faster than Nuitka.
PyPy uses a just-in-time compiler and does some very clever stuff to achieve its speed. According to reported benchmarks, it is 7.6 times faster than CPython on average. I can easily believe that. The only (slight) disadvantage is that it’s always a little behind Python versions (i.e., up to 2.7.13 (not 2.7.15) and 3.5.3 (not 3.6.5 )).
Producing an exe takes a bit of work. You have to write your Python in a subset called RPython.
Cython isn’t just a compiler for Python; it’s for a superset of Python that supports interoperability with C/C++. CPython is written in C, so it’s a language that generally mixes well with Python.
Setting things up with Cython is a little bit fiddly. It’s not like Nuitka, which just runs out of the box. First, you have to start with a Python file with a .pyx extension; you run Cython to create a pystone.c file from that:
cython pystone.pyx --embed
Don’t omit the –embed parameter. It adds in main and that is needed. Next, you compile pystone.c with this lovely line:
gcc $(python3-config --includes) pystone.c -lpython3.6m -o pystone.exe
If you get any errors, such as ‘can’t find the -lpython version,’ it could be the result of your version of Python. To see what version is installed, run this command:
pkg-config --cflags python3
After all that, Cython only gave 228,527 pystones/sec. However, Cython needs you to do a bit of work specifying the types of variables. Python is a dynamic language, so types aren’t specified; Cython uses static compilation, and using C typed variables lets it produce much better optimized code. (The documentation is quite extensive and required reading.)
Size Execution pystones/sec 1. 219,552 Kb 228,527
This is a set of scripts and modules for “freezing” Python scripts into executables, and can be found on GitHub.
I installed it and created a folder freeze to manage things in:
sudo pip3 install cx_Freeze --upgrade
One problem I found with the install script was an error missing “lz”. You need to have zlib installed; run this to install it:
sudo apt install zlib1g-dev
After that, the cx_Freeze command took the pystone.py script and created a dist folder containing a lib folder, a 5MB lib file and the pystone application file:
cxfreeze pystone.py --target-dir dist
Size Execution pystones/sec 1. 10,216 174,822
Not the fastest performance, because it’s the same speed as CPython. (Python freezing involves shipping your application in a single file (or folder) with the needed Python elements, rather than compiling; it means the target does not require Python.)
I am in awe of PyPy’s performance. Compilation was very fast, and it produced the results less than a second after I pressed the enter key. If you want an exe, though, I recommend Nuitka; it was a no-fuss compile and runs faster than CPython. Experiment with these Python compilers and see which work best for your particular needs.