5 Easy Ways To Speed Up Python | by Emmett Boudreau | Towards Data Science

Python has quickly found itself to be one of the most popular programming languages in the world, bouncing between position one and two on the TIOBE Index. Across the globe, Python is used for Data Science and machine learning, scripting, software engineering, and even web development. There are many reasons that this has become the case. First and foremost, Python is a very high-level scripting language. This makes Python incredibly easy to learn.
Another thing that should certainly be mentioned is Python’s fantastic ecosystem. Whereas most other package ecosystems in similar languages usually have less than fifty-thousand packages registered, Python has around two-hundred thousand. These packages also span a variety of different disciplines. This means that Python is most likely one of the most versatile languages used today. However, despite how great of an ecosystem Python has and how great of a language it is to use, no language without its problems.
Given Python’s high-level syntax and declarative programming nature, it can be assumed that Python is likely a scripting language. This means that instead of being compiled, it is instead actively read by a compiled programming language, which in this example is the C programming language. Luckily, C is the standard when it comes to lower-level, iterative programming. This means that Python generally gets along with a lot of other language that work with C as well as C itself. In fact, you can write Python from C.
However, with any interpreted programming language, there is always a concern of speed, and with Python there is no exception. Although Python is a pretty fast language when compared with other similar languages, it still does not match the speed of some compiled lower-level solutions like C or C++. Speed tends to be Python’s Achilles’ heel. Although the language has many strengths, the speed of the interpretation is generally regarded as its biggest weakness. However, there are some simple techniques available that can be used to speed Python up very effectively.
Iteration is one thing that can slow down computing significantly. Of course, this is the case with just about any looping, especially in languages like Python. While Python certainly does support multi-threading, generally the syntax for controlling threads and processes is a little too declarative to control them as effectively as one could in other programming languages. Since Python is pretty single-threaded by nature, iteration can often stall an entire application while it is working on that loop.
A great module that is also included in Python’s standard library for speeding up these looping techniques is Itertools. The Itertools module comes loaded with tools for looping very quickly with alternate C implementations that are provided by other packages, rather than Python.h. These functions usually result in faster iteration. I have a whole article that goes into more detail with some examples of using itertools to speed up some algorithms here you may read here:
towardsdatascience.com
Another standard library approach to speeding up Python is to use multi-threading. Python was built as a single-threaded, high-level programming language, so in a lot of ways multi-threading is going to be more difficult when it comes to doing it in Python. However, in most examples it will turn out to be a single simple call to create a new process.
The biggest problem I have found with using threads in Python is that processes are unnamed and not manageable. This means that you can create processes to handle loops and arithmetic for you while you are doing other things, but it is hard to call back to those threads and manage what they are doing. Of course, in most cases that will not be necessary, so this is certainly one that can come in handy for the average Python programmer. We can use threading by first importing the threading module from Python’s standard library:
import threading
Next we need to write a function for our new thread to target:
import time as tidef sleeper():    ti.sleep(5)    print("Hello")
Now we can construct a new object by initializing the thread.Thread class:
x = threading.Thread(target = sleeper)
We can use the target function key-word argument in order to set our target. If our function needs arguments to be provided, then we can add the args key-word argument. I will also provide an example of that:
import time as tidef sleeper(n):    ti.sleep(n)    print("Hello")x = threading.Thread(target = sleeper, args = (5))
Then we can start our thread with Thread.start():
x.start()
Needless to say, doing two things at once can be very effective when it comes to speeding up code. Though the threading implementation in Python is not the greatest in the world, it is certainly worth knowing and using!
Just In Time compilation, or JIT compilation is a compiling technique that makes the interpreted code compiled and executed and compiled at the same time. Anytime we compile a Python application, consider that there is a certain amount of overhead where data for everything in the code needs to be registered and allocated data. This is so that Python has a reference for this in each scope whenever these objects are called. JIT compilation can be used to reduce this overhead.
With JIT compilation, rather than allocating all of this data prior to running the application, we are doing it as the application is running. This reduces the compilation to execution start times, and just like how doing more than one thing at a time when it comes to threading, doing more than one thing at a time when it comes compilation can also significantly speed up the speed of code.
The package that is typically used for this in Python is a package called Numba. Something great about Numba’s implementation is that it is incredibly easy to use! Numba uses a simple decorator to let Python activate JIT on certain functions and classes. I wrote an article all about Numba that you may read if you want to learn more:
towardsdatascience.com
Another popular choice when it comes to speeding up Python is Cython. Cython is a way that you can utilize C’s speed without even knowing or writing the C language. While it of course does not come in quite as quick as just using C, it is a great way to stick with Python while also speeding things up a tad.
A lot of programmers look at Cython as a solid solution to Python’s speed, but I think this is a mistake. Cython will not work for everything in Python. There are many cases where Python will fail to properly Cythonize, and while that might be a lot less common, it is always a possibility. Handling dependencies with Cython can also be a nightmare, and that is an important thing to consider if it is planned to be used in your tech-stack. We can import the cythonize function from the Cython.Build module from the Cython package. Of course, this module is not in the standard library, so you will need to add it yourself. You also need to create a setup from the standard library tool setuptools, so I will import both:
from setuptools import setupfrom Cython.Build import cythonize
We will need to build a Cython build file, usually called setup.py. Then we can call the cythonize function with
setup(    ext_modules = cythonize("example.pyx"))
You will also want to name any Python modules you are working with to a .pyx file, rather than a .py file. Finally, to build our Cython, we will use the Python command-line-interface, like so:
python setup.py build_ext --inplace
This will create an example.so. This file is now a compiled binary that we can use from Python, we can simply import it and use it as you might expect typical in Python code.
Another solution you can choose to utilize is parallel computing. Parallel computing combines the power of a graphics card with the power of a computer processors. Before we use parallel computing, we should always consider what a graphics processor actually does. Graphics processors are specialized pieces of equipment that are good at particular tasks, and worse at others. In other words, a processor without an APU would be an awful graphics processor. Likewise, a central processor would be a terrible graphics processor.
That in mind, we should consider that graphics processors are really good at working with large arrays of numbers. This means that they are great when applied to statistical computing in machine-learning, so in many cases with Data Science, parallel computing can really provide a significant boost to speed. Earlier we touched on using Numba’s JIT compilation to speed up your code, but the Numba package also provides tools for CUDA programming. Of course, this is for Nvidia cards.
from numba import cuda
You will also need to export an environmental variable in order to use CUDA. I personally have this in my ~/.bashrc, but of course if you do not want this variable to be declared in every usage of your Bash, then you can simply put this into a terminal session:
export NUMBA_ENABLE_CUDASIM=1
Like Numba’s JIT, we simply use a decorator in order to determine if a function is going to use CUDA or not, which is incredibly easy! Even cooler, we can combine CUDA with JIT for some incredibly quick results!
from numba import cuda@cuda.jitdef example(io_array):    print(io_array)
Now that we know about parallel computing, we should consider that the language’s implementation of this is certainly not the best in the world. There are shortcomings to Python, and parallel computing is most certainly one of them. It is always going to be more difficult to work with CUDA in Python than other languages, as its base was never built with parallel computing in mind. This is one great thing about Julia, it was built at a time when parallel computing was becoming popular. I have an article detailing how to use its CUDA implementation, for those interested in Julia you may read here:
towardsdatascience.com
Another huge negative is that you need a graphics processing unit in order to actually use parallel computing. Graphics cards are some of the most expensive hardware to have in a computer, so getting one that is apt for helping your code run fast can be quite difficult, which is a big downside to this technique. Furthermore, if you are like me and love the convenience of a laptop, it is likely you will not find a very light laptop with a graphics processor inside. Personally, I like laptops in the 11–13-inch range, but I usually end up working on a bulky 15" laptop, which is not my preference when it comes to computers.
Another solution that is incredibly easy to use is FuncTools. This module is in the standard library, and it is so versatile that it makes it hard to cover all that is in it. There are many decorators that the package provides that can really speed up your code, or just make your syntax more user-friendly. I think Functools is certainly a package worth knowing.
As I touched on, FuncTools has a lot of abilities. For example, the package can make Python use single-dispatch, which alters the way that types are used along with functions. The module itself is really awesome and very useful in so many situations that there is not really a good reason not to use it. The module also provides a caching decorator that allows you to cache values in Python for use later, which is very useful for a function that is used frequently in your code.
If none of these solutions seem to work for you, you can always intermittently drop into C. One of Python’s greatest features is that it is built in C, so C can often be used as a fallback for the language in times where speed might be needed. You also do not need to write all of your code in C, and can call to it from Python. Of course, this is going to be a dramatic shift from the other solutions on this list. C is a great language, but its imperative nature does make it take a lot longer to write.
Python is a great language. The language has become so popular recently for a variety of reasons, and is a great choice when it comes to machine-learning applications. However, machine-learning can be hard on hardware, and Python can often bog down compared to compiled languages like C and C++. However, using these techniques you can very easily improve the speed of Python dramatically, and make its application to certain problems actually play out pretty close to the speed of C. Thank you for reading my article, and happy programming!