Why Optimising Python is Hard

Python is infamous for being “slow”. What is usually meant by this is that Python programs run slower than comparable programs written in C/C++ or Fortran. The primary reason given is that Python programs are interpreted, rather than compiled to machine code. This is, however, only part of the story. Nevertheless, the question remains: why is Python not just compiled to optimised machine code?

In fact, compiling a Python program to plain machine code is an almost trivial exercise, yet an entirely futile one. There is absolutely nothing to be gained, unless we also manage to do significant optimisations during the compilation process. The speed of C, say, does not stem from the fact that it is compiled to machine code alone. Rather, the compiler discovers situations where, for instance, a variable can be entirely kept in a processor’s internal registers, and needs none of the slow memory operations at all. Doing the same trick with Python, however, is much harder.

The bottleneck that makes optimisations in Python so difficult is static analysis of the code. Take a code snippet like a + b, for instance. Depending on what a and b refer to, this can be the addition of two integer values, of floating point values, the concatenation of two strings or lists, or – even worse – an operation on a yet unknown datatype. As long as the compiler has to expect every eventuality, opportunities for optimisation are scarce.

The question I am addressing in this series of articles is in fact: why is it so difficult for the Python compiler to figure out what a and b in the code snippet above are referring to?

Why Optimising Python is Hard (1): Dynamic Execution – Using Python’s exec function, we can dynamically change what names in the program mean in unpredictable ways.

Why Optimising Python is Hard (2): Messing with Namespaces – On second glance, we do not even need the exec function to modify the meaning of names in Python.

Why Optimising Python is Hard (3): Tail Call Optimisation – Recursive functions might not be (directly) recursive, after all; even a function calling itself could actually call something entirely different.

to be continued

Please note: Limiting the evaluation and judgement of any one programming language to raw execution speed (or any other single criterion) is, of course, nonsense. First and foremost, a proper evaluation would have to be based on a specific goal to be achieved. The intent of my research and articles is, however, not (!) to assess why Python is a good, or bad language. Again, that would require to identify a goal first. Rather, in these articles, I am trying to explain where the challenges are with respect to ahead-of-time optimisations.

Related articles

These articles cover some of the basics, or give additional details for the articles abvove. They are, however, self containing, and can mostly be read in their own right.

Implementing Code Transformations in Python – If you want to implement optimisations in Python, it is certainly a good idea to know how to transform the abstract syntax tree.

Closures in Python – A first look at what happens if you nest functions, and how a function can access the local variables of its surrounding function.

to be continued