Speeding up Python with NIM
Lee Hawthorn April 29, 2025 #Nim #PythonWe see in the Python community there is a big push to get it faster by disabling the GIL - this will probably see benefits for CPU-bound calculations, but only time will tell.
I wanted to explore how far we can go with regular Python, using native code.
One popular way to benchmark this is to calculate Fibonacci sequence.
The naive way to compute this is with recursion:
return
return
Instead of using recursion, we can flatten the code and use a loop to improve performance.
return 0
return 1
= 1, 1
,
= , +
, return
= 50
=
=
With my laptop, this outputs Python Seconds Elapsed: 0.000187
Single-threaded, I was surprised how fast this executed (Python 3.13).
This shows how we should try and optimize the code we have before thinking about running on lots of cores.
Nevertheless, I wanted to see the difference using native code.
If you want to give this a go on your own machine, we will need to install NIM and UV.
I set up this code with NIM file: pure_fib.nim
- this language is compiled to C, so we expect a boost. I tried to keep the code similar.
import times
proc fib(n: int): int =
var prev = 0
var curr = 1
:
let next = prev + curr
prev = curr
curr = next
return curr
# Main block to call fib and time its execution
when isMainModule:
var result: int = 0
var n: int = 50
let startTime = cpuTime() # Record the start time
:
result = fib(a)
echo result # Call the fib function
let endTime = cpuTime() # Record the end time
echo "Fibonacci(50): ", result
echo "Execution time: ", (endTime - startTime), " seconds"
You can compile this code with: nim c -d:release pure_fib.nim
To calculate a fib of 50, this takes: Execution time: 0.000140 seconds
, which is a bit faster than Python.
We can use this function in Python by making a small change to the code.
Initialize a UV project in a folder of your choice. First, make a file called .python-version
and enter the value 3.13
.
This will tell UV to install Python 3.13 into the project.
CD into your project folder and type uv init
.
Activate your environment.
First, install nimpy:
nimble install nimpy
We need to add a PyPI
library called nimporter
, and this needs setuptools
. We can do this with UV:
uv add nimporter
uv add setuptools
After running uv add
, there will be a .venv
environment created in your project folder. By keeping this isolated, you will have an easier cleanup later.
Change the NIM file by adding import nimpy
and adding a pragma {.exportpy.}
- this is how you export the procedure for use in Python.
import nimpy
import times
proc fib(n: int): int {.exportpy.} =
var prev = 0
var curr = 1
:
let next = prev + curr
prev = curr
curr = next
return curr
# Main block to call fib and time its execution
when isMainModule:
var result: int = 0
var n: int = 50
let startTime = cpuTime() # Record the start time
:
result = fib(a)
echo result # Call the fib function
let endTime = cpuTime() # Record the end time
echo "Fibonacci(50): ", result
echo "Execution time: ", (endTime - startTime), " seconds"
We will use a new Python file to test this nim proc. Create main.py
in the same folder as the nim file.
# Nim imports
= 50
=
=
When you run this code, the nim file will be compiled, and a binary library will be stored in the __pycache__
folder. Run the Python for a second time.
I got: Execution time: 0.0001198939 seconds
.
In summary, we can see:
Type | MS |
---|---|
Pure Python | 187 |
Pure Nim | 140 |
Python/Nim | 120 |
Note the fib function when used in Python is compiled as a library, and we're calling it from Python 50 times. I didn't bother to use any caching/memoization as these speeds are already fast. As an exercise, you can add caching in Python and/or NIM to see if it makes a difference.
The nimporter
library is much more powerful. It actually lets you bundle up your Python/Nim as a PyPI
package. Users don't have to have Nim installed to use it - similar to how we don't need a C compiler to use Numpy.
In summary, we can optimize Python code to speed up execution. Nim code is not too different from Python code either. I believe Nim is an option open to Python developers to use native code to get faster execution of code, where it's needed.