Introduction to Codon, the Python Compiler

Rocks

Compiler for Python

by Craig Miller

Why Python

Python is one of the most commonly used computer languages in the world.It is mature, feature rich, and even comes standard on PiOS.

Some might say an advantage of Python is that it is an interpretive language. This certainly assists in coding, where one is editing/running/debugging/fixing/running again. Development of interpreted languages can be faster without having to wait for the sometimes lengthy compiling step.

The downside of an interpreted language is that it has to, well, interpret each line, and that takes time. Sure there are pyc files but those are just byte compiled, they are not native machine compiled, so at the end of the day pyc files are still interpreted, which means slower execution.

Python Compilers

Codon isn't the first attempt at compiling Python. There are other options such as:

Installing Codon on x86

Codon is great to run inside a Linux Container (LXD), as it is fairly self contained, and you can then move the compiled binary to another compatible architecture (e.g. x86_64 or aarch64). Since the installation requires root, and trust in the install script, I find it safer to run inside a container.

Create a container (use Debian, life will be easier), and then step inside the container.

lxc exec codon bash

Then execute the install script (for Linux x86 or MacOS)

/bin/bash -c "$(curl -fsSL https://exaloop.io/install.sh)"

Before running codon, a c compiler needs to be installed

apt install gcc zlib1g-dev

Installing Codon on the Pi (aarch64)

Installing codon on the Pi takes a little more work. First you must download a pre-built binary of codon from Google Drive. Ignore the fact that Google Drive can't check for viruses.

Then scp the downloaded tar file over to your Pi (I am using a linux container), and untar the file.

tar xvf codon-linux-aarch64.tar.gz
mv build .codon

Add codon to your path

export PATH=/home/$USER/.codon:$PATH

Test codon can run

$ codon --version
0.15.5

Using Codon

Set your path to include Codon

$ export PATH=/home/$USER/.codon/bin:$PATH

 which codon
/home/pi/.codon/bin/codon

Pull down a simple example of discovering primes.

#! /usr/bin/env python3

#
# Example from https://github.com/exaloop/codon
#

from sys import argv

def is_prime(n):
    factors = 0
    for i in range(2, n):
        if n % i == 0:
            factors += 1
    return factors == 0

limit = int(argv[1])
total = 0

@par(schedule='dynamic', chunk_size=100, num_threads=16)
for i in range(2, limit):
    if is_prime(i):
        total += 1

print(total)

Codon can run the code directly, or build a binary which you can run later without codon. To run the python script:

$ codon run prime.py 100
25

To see how much time it took to run the program, use the time command.

$ time codon run prime.py 100
25

real    0m1.682s
user    0m1.715s
sys 0m0.561s

To see how fast native Python would run the same script:

$ time ./prime.py 100
25

real    0m0.022s
user    0m0.016s
sys 0m0.006s

It would appear that native Python is faster! But wait, time is including the compilation time and run time.

To test just the run time, we need to build a stand alone compile application.

codon build prime.py
$ time ./prime 100
25

real    0m0.007s
user    0m0.000s
sys 0m0.007s

Now we can see that the compiled version is 3x faster than running native Python interpreter.

You can run larger input numbers, and see where codon can significantly speed up your applications. I achieved a speed increase of 51x using multi-threading.

What Codon can do, and can't do

The developers of codon have focused their work on the parts of Python which will benefit the most from compiling. Trying to compile a Python script (such as ipv6-httpd) which includes modules that codon doesn't support results in errors:

$ codon run ipv6-httpd.py 
ipv6-httpd.py:24:8-14: error: no module named 'socket'
ipv6-httpd.py:25:6-17: error: no module named 'http.server'
ipv6-httpd.py:25:6-17: error: no module named 'http.server'
ipv6-httpd.py:27:8-14: error: no module named 'signal'
ipv6-httpd.py:35:5-16: error: cannot import name '_exit' from 'os.__init__'
ipv6-httpd.py:38:1-45: error: no module named 'signal'
ipv6-httpd.py:41:17-41: error: name 'SimpleHTTPRequestHandler' is not defined
ipv6-httpd.py:55:20-30: error: name 'HTTPServer' is not defined
ipv6-httpd.py:59:5-18: error: name 'server' is not defined
ipv6-httpd.py:63:5-16: error: cannot import name '_exit' from 'os.__init__'

As you can see in the errors above, many modules are not found.

So what modules does codon support? Codon is installed with a small subset of compiled Python modules. At this time, there is no easy way for non-devs to compile their own modules. The list of supported modules can be found in $HOME/.codon/lib/codon/stdlib/

ls .codon/lib/codon/stdlib/
algorithms         datetime.codon   gzip.codon       openmp.codon    random.codon      sys.codon
bisect.codon       experimental     heapq.codon      operator.codon  re.codon          threading.codon
cmath.codon        functools.codon  internal         os              sortedlist.codon  time.codon
collections.codon  getopt.codon     itertools.codon  pickle.codon    statistics.codon  typing.codon
copy.codon         gpu.codon        math.codon       python.codon    string.codon      unittest.codon

As you can see many of the modules are cpu-intensive, precisely where compiling would see the most improvement.

Running hybrid code

Codon does support the running code which part is compiled, and other parts are interpreted.

Install the libpython library, if not already installed

sudo apt install libpython3.9

Find the path of the libpython

dpkg -L libpython3.9
...
/usr/lib/x86_64-linux-gnu/libpython3.9.so.1

Tell codon where to find the libpython library

export CODON_PYTHON=/usr/lib/x86_64-linux-gnu/libpython3.9.so.1

Then run your carefully constructed code:

#!/usr/bin/env python3

"""
    Python 3 codon example
"""

# import codon's time module
import time
# import random module from python
from python import random


FALSE = 0
end = FALSE

def myrandom():
    print("rand=" , random.randrange(1000))

print ('\nPress ^C to quit')

i = 1
while not end:
    print("counting " + str(i))
    i+=1
    if i >= 20:
        break
    myrandom()
    time.sleep(1)

In the above code, codon will use the native Python random module.

$ codon run hybrid2.py 

Press ^C to quit
counting 1
rand= 673
counting 2
rand= 766
counting 3
rand= 101
^C

I was not able to get codon to do callback functions such as signal handling or curses (for keyboard entry). Codon would just fail on the signal, and there didn't appear to be an easy way around this problem.

Compiling Python, can be a good thing

If you have a CPU-intensive application, which you can split between the CPU-intensive part, and other parts, then codon may be the accelerator you have been looking for. But I wouldn't suggest trying it on a complex program, you will rapidly find yourself repeatedly hacking your code in an attempt to clear the codon compiling errors.


Notes:

20 June 2023