Sommaire
- Introduction
- 1. Version de python
- 2. Où se situent vos instructions ?
- 3. Optimisez vos listes
- 4. Entrées/Sorties (input/output)
- 5. Cython : Optimiser Python avec du C ?
- Conclusion
Introduction
Tous les calculs ont été réalisés sur Python3.10
Cet article ne traite pas de l'optimisation des algorithmes, mais plutôt de l'optimisation spécifique à Python. Nous allons explorer toutes les optimisations que j'ai découvertes au cours de mon expérience avec ce langage.
1. Version de Python
En fonction des versions, Python est plus ou moins rapide. Il est important d'utiliser la dernière version de Python si vous voulez profiter de toutes les optimisations faites par les développeurs.
Par exemple, entre Python 3.10 et 3.11, les développeurs ont mesuré une accélération moyenne de x1.25, ce qui n'est pas négligeable dans notre quête d'optimisation.
2. Où se situent vos instructions ?
Lorsqu'on débute en programmation, on a souvent tendance à tout mettre dans un fichier sans y réfléchir.
Cependant, saviez-vous que regrouper l'ensemble de votre code au sein d'une fonction peut accélérer l'exécution de votre programme ?
En enveloppant simplement nos instructions à l'intérieur d'une fonction, on peut gagner jusqu'à environ 30% en termes de rapidité !
Ici, on passe de 7.9 secondes à 5.8 secondes !
3. Optimisez vos listes
Les listes sont l'un des éléments les plus couramment utilisés en développement. Cependant, en Python, il existe de nombreuses optimisations pas forcément connues.
Objectif : Créer une liste d'éléments de 0 à 100 000 000.
Pour mes tests, j'ai exécuté chaque fonction 20 fois de suite afin d'obtenir des valeurs plutôt précises, en utilisant le module timeit.
# Exemple de code avec timeit
import timeit
def my_function():
pass # Your code ...
print(timeit.timeit(my_function, number=20))
3.1. Concaténation
def concat_function():
liste = []
for i in range(100_000_00):
liste + [i]
Temps : 13 seconde (environ)
Dans cet exemple, Python doit allouer de la mémoire pour la liste [i]
, puis il doit à nouveau allouer de la mémoire pour recréer la liste finale à chaque itération, ce qui ralentit considérablement l'exécution.
3.2. Append
def append_function():
liste = []
for i in range(100_000_00):
liste.append(i)
Temps : 11.5 seconde (environ)
Python a mieux optimisé la fonction append
, la rendant légèrement plus rapide. Cependant, lorsque la liste devient trop grande, Python alloue davantage de mémoire pour agrandir la liste et réécrire toutes les valeurs dans un tableau plus grand.
3.3. Les listes en compréhension
Les listes en compréhension sont une manière concise de créer des listes en Python. Bien que cela puisse sembler être une préférence des puristes souhaitant exhiber leur connaissance de Python, cette méthode présente en réalité un réel intérêt en termes d'optimisation.
def comprehention_function():
liste = [i for i in range(100_000_00)]
Temps : 7.5 secondes (environ)
L'utilisation de listes en compréhension permet de gagner un temps considérable. C'est la méthode que je vous recommande d'utiliser. Avec un peu d'entraînement, elle devient facile à maîtriser.
3.4. Utilisation de la bibliothèque NumPy
NumPy est une bibliothèque spécialisée en Python dans la construction de tableaux. En effet, j'ai bien dit "tableaux" et non "listes", mais quelle est la différence ?
Les tableaux sont des structures de données à plage de données fixe. Il est impossible de changer leur taille. Cependant, il faut spécifier leur taille initiale.
Les listes, quant à elles, sont des structures de données variables. Elles permettent de changer leur taille dynamiquement, sans nécessité de spécifier la taille du tableau à l'avance.
Comme NumPy est une bibliothèque qui utilise exclusivement des tableaux et qu'elle est partiellement écrite en C, ce qui est bien plus rapide que Python, les performances obtenues peuvent être impressionnantes.
import numpy as np
def numpy_function():
liste = np.arange(100_000_00)
Temps : 0.25 seconde (environ)
Oui, vous avez bien lu : dans certains cas, l'utilisation de tableaux de NumPy peut drastiquement accélérer l'exécution de votre programme.
4. Entrées/Sorties (input/output)
Un élément qui peut ralentir la vitesse de votre programme concerne les opérations de lecture/écriture, plus couramment utilisées sous print()
et input()
.
4.1. La fonction print()
Il existe une fonction un peu moins connue qui améliore la rapidité de l'écriture dans le terminal. Elle permet de gagner environ 50% de temps.
import sys
sys.stdout.write("<votre text>")
Cette fonction ne prend qu'un unique paramètre, ce qui peut être plus contraignant, mais elle est plus rapide.
Cette fonction n'ajoute pas automatiquement le caractère \n
à la fin du texte.
4.2. La fonction input()
La fonction input()
est relativement lente car elle gère des fonctionnalités d'affichage avancées pour le terminal. Ainsi, l'appeler de nombreuses fois peut considérablement augmenter le temps d'exécution.
Cependant, il existe une alternative bien plus rapide à cette fonction, qui est jusqu'à 8 fois plus rapide que la précédente.
import sys
ligne = sys.stdin.readline()
Il est important de noter que cette fonction laisse le caractère \n
(retour à la ligne) à la fin de l'entrée.
4.3. Remplacement des Fonctions de Base
Vous pouvez remplacer les fonctions de base par celles qui sont optimisées, comme ceci :
import sys
input = sys.stdin.readline
print = sys.stdout.write
5. Cython : Optimiser Python avec du C ?
La bibliothèque Cython est un outil très puissant qui permet de convertir du code Python en code C, ce qui en fait une ressource précieuse pour optimiser votre code. Cependant, elle peut être un peu déroutante à utiliser pour les débutants. Dans cette section, je vais vous présenter un mini tutoriel pour vous aider à démarrer avec Cython.
Avant d'entrer dans les détails, jetons un coup d'œil à ce que nous pouvons obtenir en appliquant l'accélération avec Cython à un objectif majeur de Python :
5.1. Petit Test de Cython (sans optimisation)
Comparons deux programmes, un en python classique, et l'autre utilisant Cython
Fonction | Python | Cython |
---|---|---|
concaténation | 13 secondes | 10.18 secondes |
append | 11.5 secondes | 4.79 secondes |
compréhension | 7.5 secondes | 4.7 secondes |
NumPy | 0.25 seconde | 0.25 seconde |
Nous pouvons observer une nette amélioration des performances pour les trois premières fonctions grâce à Cython. Cependant, pour NumPy, la différence n'est pas significative, car cette bibliothèque est déjà optimisée en C.
De plus, il est important de noter que nous n'avons pas encore effectué d'optimisations avancées avec Cython. Il est possible de typer chaque variable pour optimiser l'exécution des fonctions et les rendre encore plus rapides !
5.2. Utilisation de Cython
Commençons par installer les dépendances nécessaires.
pip install cython setuptools
Ensuite, créons notre fichier nom_fichier.py.
Une fois votre fichier créé, renommez-le en .pyx.
L'extension .pyx est associée à un fichier Python qui sera optimisé avec Cython. Cette étape est obligatoire pour pouvoir compiler le fichier Python.
Ensuite, créons notre "constructeur" de fichier C.
Créez et nommez votre nouveau fichier setup.py.
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("nom_fichier.pyx")
)
Ensuite, dans votre terminal, exécutez la commande suivante :
python3 setup.py build_ext --inplace
Après cette étape, vous obtenez un fichier en .c et un fichier en .so.
Le fichier .so fait la liaison entre votre fichier .pyx et .c.
Ensuite, votre fichier nom_fichier.pyx agit comme une librairie écrite en C.
Supposons que dans mon fichier nom_fichier.pyx, j'ai ce code :
def append_function():
liste = []
for i in range(100_000_00):
liste.append(i)
return liste
def main():
time_exec_append = timeit.timeit(append_function, number=20)
print(f"append_function: {round(time_exec_append,2)} secondes")
Alors, dans un fichier test.py, il vous suffit d'écrire :
import nom_fichier
nom_fichier.main()
En exécutant la fonction principale (main) et en enregistrant le temps d'exécution du fichier, vous verrez déjà une nette amélioration des performances ! (cf. Conclusion)
Bien entendu, vous pouvez explorer toutes les façons d'optimiser Cython pour rendre votre code encore plus rapide !
Conclusion
Fonction | Python3.10 (Hors fonction) | Python3.11 (Hors fonction) |
---|---|---|
concaténation | 17.31 secondes | 20.12 secondes |
append | 16.18 secondes | 14.42 secondes |
compréhension | 7.7 secondes | 6.2 secondes |
NumPy | 0.31 seconde | 0.33 seconde |
Fonction | Python3.10 (Dans une fonction) | Cython (Python3.10) |
---|---|---|
concaténation | 13.08 secondes | 10.18 secondes |
append | 11.5 secondes | 4.79 secondes |
compréhension | 7.5 secondes | 4.7 secondes |
NumPy | 0.25 seconde | 0.25 seconde |
Voici un tableau récapitulatif de la plupart des améliorations
Il est important de noter que les temps indiqués dans le tableau sont indicatifs. Ils dépendent de la puissance de votre ordinateur, de la charge de travail de votre machine et d'autres facteurs. Cependant, en suivant ces conseils, vous pourrez observer des améliorations significatives des performances de vos programmes Python.
Notez que les temps sont à prendre avec des pincettes car elle dépende de votre ordinateur et des applications en cours sur votre machine