On the 16th of November 2022, Python 3.11 was released. One of the key highlights is that it is much faster. This article will put that into perspective by analysing Fibonacci algorithm across multiple releases.

**For this particular case Python 3.11 is more than 2x faster than 3.10. **

That being said the devil is in the details. Here is the code that we used to test:

def Fib(n): k = [1,1,1] if n==1 or n==2: return 1 else: for idx in range(2,n): k[2] = k[1] + k[0] k[0] = k[1] k[1] = k[2] return k[2]

Granted that there are other ways of implementing Fibonacci, but this code does run 2 times fast than 3.10. To test this we used:` pytest pytest-benchmar`

k and `nox`

`Pytest`

is a commonly used testing framework with a wide variety of plugins. The `Pytest-benchmark`

is useful for performance benchmarking, enabling us to write a simple test:

def test_fib(benchmark): result = benchmark(Fib, 40) assert result == 102334155

There are several ways of running multiple versions of Python but we used `nox`

, as it is easy to setup and does not need a cloud machine to test it. Please note for performance testing, it can be tricky if you do not use your own laptop. These tests were done on a MacBookPro, intel based processor – i5.

import nox @nox.session(python=["3.7","3.8", "3.9","3.10", "3.11"]) def pythonRun(session): session.install("pytest", "pytest-benchmark") session.run("pytest", "Fib.py", "--benchmark-save=fib")

This short video shows how to setup testing multiple versions of python with `nox`

.

It is true, that Python did get faster, and that will have implications in many projects, but in the technical computing world where many people use `numpy`

and `pandas`

this may not always be so clear as in this example. You could also get much different results by rewriting the code slightly differently. So here is the same problem written in Python 3.11 with different algorithms.

import math import numpy as np import pytest_benchmark def myFib1(n): if n==1 or n==2: return 1 else: return myFib1(n-1)+myFib1(n-2) def myFib2(n): k = [1, 1] if n==1 or n==2: return 1 else: for idx in range(2,n): k.append(k[idx-1]+k[idx-2]) return k[len(k)-1] def myFib3(n): k = [1, 1, 1] if n==1 or n==2: return 1 else: for idx in range(2,n): k[2] = k[0] + k[1] k[0] = k[1] k[1] = k[2] return k[2] def myFib4(n): sqrt = math.sqrt(5) return int((( (1 + sqrt) ** n - (1 - sqrt) ** n ) / ( 2 ** n * sqrt ))) def myFib5(n): sqrt = np.sqrt(5) return int((( (1 + sqrt) ** n - (1 - sqrt) ** n ) / ( 2 ** n * sqrt )))

Algorithm | Median (ns) | Times slower |
---|---|---|

myFib4 | 540.7550 | 1.0 |

myFib5 | 1,931.3350 | 3.57 |

myFib3 | 2,691.3950 | 4.98 |

myFib2 | 2,877.5600 | 5.32 |

myFib1 | 10,888,798,281.9942 | >1000 |

This table shows that `myFib2`

is **5.32x** slower than `myFib`

4. Which means that in certain cases, changing the algorithm or implementation actually might have a bigger impact than python version.

The table below shows all the combinations of different versions of Python and different implementations.

Python Version | Algorithm | Median (ns) | Times slower |
---|---|---|---|

3.11 | myFib4 | 545.9000 | 1.0 |

3.10 | myFib4 | 757.5350 | 1.39 |

3.8 | myFib4 | 888.4550 | 1.63 |

3.7 | myFib4 | 905.0950 | 1.66 |

3.9 | myFib4 | 966.3400 | 1.77 |

3.11 | myFib5 | 1,902.3650 | 3.48 |

3.10 | myFib5 | 2,123.5650 | 3.89 |

3.8 | myFib5 | 2,295.3250 | 4.20 |

3.9 | myFib5 | 2,372.3300 | 4.35 |

3.7 | myFib5 | 2,721.5750 | 4.99 |

3.11 | myFib2 | 2,799.3750 | 5.13 |

3.11 | myFib3 | 2,985.3200 | 5.57 |

3.10 | myFib2 | 5,139.5000 | 9.41 |

3.9 | myFib2 | 5,373.6350 | 9.84 |

3.8 | myFib2 | 5,388.9600 | 9.87 |

3.8 | myFib3 | 5,698.5750 | 10.44 |

3.9 | myFib3 | 5,828.6600 | 10.68 |

3.10 | my/Fib3 | 6,019.7400 | 11.03 |

3.7 | myFib2 | 6,026.5650 | 11.04 |

3.7 | myFib3 | 6,098.7050 | 11.17 |

3.11 | myFib1 | 10,593,028,328.0031 | >1000 |

3.10 | myFib1 | 19,697,730,521.0021 | >1000 |

3.8 | myFib1 | 21,099,857,967.0000 | >1000 |

3.9 | myFib1 | 22,114,287,559.0000 | >1000 |

3.7 | myFib1 | 24,175,061,539.0000 | >1000 |

When analysing these results, it is important to call out that performance can be achieved by changing the algorithm and the python version. We should in general avoid stating that Python 3.11 is X% faster than 3.10, as it does depend heavily on implementation. As you can see that the `myFib4`

in 3.10 is **34%** slower than in 3.11, where `myFib3`

is twice as slow. One other clear conclusion is that Python is significantly slower when implementing recursive functions.

Please note that we are using the median in nanoseconds, as running multiple times the same algorithm, the median is in general the right way to measure performance. This article also did not compare OS or hardware architectures, but they too can influence the results.