Fermer

août 4, 2022

Un guide du multitraitement Python et de la programmation parallèle

Un guide du multitraitement Python et de la programmation parallèle


Accélérer les calculs est un objectif que tout le monde veut atteindre. Que se passe-t-il si vous avez un script qui pourrait s’exécuter dix fois plus vite que son temps d’exécution actuel ? Dans cet article, nous examinerons le multitraitement Python et une bibliothèque appelée multiprocessing. Nous parlerons de ce qu’est le multitraitement, de ses avantages et de la manière d’améliorer le temps d’exécution de vos programmes Python en utilisant la programmation parallèle.

Bon, alors allons-y !

Une introduction au parallélisme

Avant de plonger dans le code Python, nous devons parler de traitement en parallèlequi est un concept important en informatique.

Habituellement, lorsque vous exécutez un script Python, votre code devient à un moment donné un processus et le processus s’exécute sur un seul cœur de votre CPU. Mais les ordinateurs modernes ont plus d’un cœur, alors que diriez-vous si vous pouviez utiliser plus de cœurs pour vos calculs ? Il s’avère que vos calculs seront plus rapides.

Prenons cela comme un principe général pour l’instant, mais plus tard, dans cet article, nous verrons que ce n’est pas universellement vrai.

Sans entrer dans trop de détails, l’idée derrière le parallélisme est d’écrire votre code de telle manière qu’il puisse utiliser plusieurs cœurs du CPU.

Pour faciliter les choses, regardons un exemple.

Calcul parallèle et série

Imaginez que vous avez un énorme problème à résoudre et que vous êtes seul. Vous devez calculer la racine carrée de huit nombres différents. Que fais-tu? Eh bien, vous n’avez pas beaucoup d’options. Vous commencez par le premier chiffre et vous calculez le résultat. Ensuite, vous continuez avec les autres.

Et si vous aviez trois amis bons en maths prêts à vous aider ? Chacun d’eux calculera la racine carrée de deux nombres, et votre travail sera plus facile car la charge de travail est répartie équitablement entre vos amis. Cela signifie que votre problème sera résolu plus rapidement.

Bon, alors tout est clair ? Dans ces exemples, chaque ami représente un cœur du CPU. Dans le premier exemple, la tâche entière est résolue séquentiellement par vous. C’est appelé informatique en série. Dans le deuxième exemple, puisque vous travaillez avec quatre cœurs au total, vous utilisez traitement en parallèle. L’informatique parallèle implique l’utilisation de processus parallèles ou de processus divisés entre plusieurs cœurs dans un processeur.

Illustration informatique série et parallèle

Modèles pour la programmation parallèle

Nous avons établi ce qu’est la programmation parallèle, mais comment l’utilisons-nous ? Eh bien, nous avons déjà dit que le calcul parallèle implique l’exécution de plusieurs tâches sur plusieurs cœurs du processeur, ce qui signifie que ces tâches sont exécutées simultanément. Il y a quelques questions que vous devriez considérer avant d’aborder la parallélisation. Par exemple, y a-t-il d’autres optimisations qui pourraient accélérer nos calculs ?

Pour l’instant, prenons pour acquis que la parallélisation est la meilleure solution pour vous. Il y a principalement trois des modèles en calcul parallèle :

  • Parfaitement parallèle. Les tâches peuvent être exécutées indépendamment et n’ont pas besoin de communiquer entre elles.
  • Parallélisme de la mémoire partagée. Les processus (ou threads) doivent communiquer, ils partagent donc un espace d’adressage global.
  • Transmission de messages. Les processus doivent partager des messages en cas de besoin.

Dans cet article, nous allons illustrer le premier modèle, qui est aussi le plus simple.

Python Multiprocessing : Parallélisme basé sur les processus en Python

Une façon d’atteindre le parallélisme en Python est d’utiliser le module multitraitement. La multiprocessing Le module vous permet de créer plusieurs processus, chacun d’eux avec son propre interpréteur Python. Pour cette raison, le multitraitement Python réalise un parallélisme basé sur les processus.

Vous avez peut-être entendu parler d’autres bibliothèques, comme threading, qui est également intégré à Python, mais il existe des différences cruciales entre eux. La multiprocessing module crée de nouveaux processus, tandis que threading crée de nouveaux fils.

Dans la section suivante, nous examinerons les avantages de l’utilisation du multitraitement.

Avantages de l’utilisation du multitraitement

Voici quelques avantages du multitraitement :

  • meilleure utilisation du processeur lors de tâches gourmandes en ressources processeur
  • plus de contrôle sur un enfant par rapport aux threads
  • facile à coder

Le premier avantage est lié aux performances. Étant donné que le multitraitement crée de nouveaux processus, vous pouvez mieux utiliser la puissance de calcul de votre processeur en répartissant vos tâches entre les autres cœurs. De nos jours, la plupart des processeurs sont des processeurs multicœurs, et si vous optimisez votre code, vous pouvez gagner du temps en résolvant des calculs en parallèle.

Le deuxième avantage concerne une alternative au multitraitement, qui est le multithreading. Les threads ne sont pas des processus cependant, et cela a ses conséquences. Si vous créez un thread, il est dangereux de le tuer ou même de l’interrompre comme vous le feriez avec un processus normal. Étant donné que la comparaison entre le multitraitement et le multithreading n’entre pas dans le cadre de cet article, je vous encourage à poursuivre votre lecture.

Le troisième avantage du multitraitement est qu’il est assez facile à mettre en œuvre, étant donné que la tâche que vous essayez de gérer est adaptée à la programmation parallèle.

Premiers pas avec le multitraitement Python

Nous sommes enfin prêts à écrire du code Python !

Nous allons commencer par un exemple très basique et nous l’utiliserons pour illustrer les aspects fondamentaux du multitraitement Python. Dans cet exemple, nous aurons deux processus :

  • La parent traiter. Il n’y a qu’un seul processus parent, qui peut avoir plusieurs enfants.
  • La child traiter. Ceci est engendré par le parent. Chaque enfant peut aussi avoir de nouveaux enfants.

Nous allons utiliser le child processus pour exécuter une certaine fonction. De cette façon, le parent peut poursuivre son exécution.

Un exemple simple de multitraitement Python

Voici le code que nous utiliserons pour cet exemple :

from multiprocessing import Process

def bubble_sort(array):
    check = True
    while check == True:
      check = False
      for i in range(0, len(array)-1):
        if array[i] > array[i+1]:
          check = True
          temp = array[i]
          array[i] = array[i+1]
          array[i+1] = temp
    print("Array sorted: ", array)

if __name__ == '__main__':
    p = Process(target=bubble_sort, args=([1,9,4,5,2,6,8,4],))
    p.start()
    p.join()

Dans cet extrait, nous avons défini une fonction appelée bubble_sort(array). Cette fonction est une implémentation vraiment naïve de l’algorithme de tri Bubble Sort. Si vous ne savez pas ce que c’est, ne vous inquiétez pas, car ce n’est pas si important. La chose cruciale à savoir est que c’est une fonction qui fait du travail.

La classe Processus

De multiprocessingnous importons la classe Process. Cette classe représente une activité qui sera exécutée dans un processus séparé. En effet, vous pouvez voir que nous avons passé quelques arguments :

  • target=bubble_sortce qui signifie que notre nouveau processus exécutera le bubble_sort fonction
  • args=([1,9,4,52,6,8,4],)qui est le tableau passé en argument à la fonction cible

Une fois que nous avons créé une instance de la classe Process, nous n’avons qu’à démarrer le processus. Cela se fait en écrivant p.start(). À ce stade, le processus est lancé.

Avant de quitter, nous devons attendre que le processus enfant termine ses calculs. La join() La méthode attend que le processus se termine.

Dans cet exemple, nous avons créé un seul processus enfant. Comme vous pouvez le deviner, nous pouvons créer plus de processus enfants en créant plus d’instances dans le Process classer.

La classe Piscine

Que se passe-t-il si nous devons créer plusieurs processus pour gérer des tâches plus gourmandes en CPU ? Avons-nous toujours besoin de commencer et d’attendre explicitement la fin ? La solution ici est d’utiliser le Pool classer.

La Pool La classe vous permet de créer un pool de processus de travail, et dans l’exemple suivant, nous verrons comment l’utiliser. Voici notre nouvel exemple :

from multiprocessing import Pool
import time
import math

N = 5000000

def cube(x):
    return math.sqrt(x)

if __name__ == "__main__":
    with Pool() as pool:
      result = pool.map(cube, range(10,N))
    print("Program finished!")

Dans cet extrait de code, nous avons un cube(x) fonction qui prend simplement un entier et renvoie sa racine carrée. Facile, non ?

Ensuite, nous créons une instance de Pool class, sans spécifier d’attribut. La classe pool crée par défaut un processus par cœur de processeur. Ensuite, nous exécutons le map méthode avec quelques arguments.

La map méthode applique la cube fonction à chaque élément de l’itérable que nous fournissons – qui, dans ce cas, est une liste de tous les nombres de 10 à N.

L’énorme avantage est que les calculs sur la liste se font en parallèle !

Tirer le meilleur parti du multitraitement Python

Créer plusieurs processus et effectuer des calculs parallèles n’est pas nécessairement plus efficace que le calcul en série. Pour les tâches à faible consommation de CPU, le calcul en série est plus rapide que le calcul en parallèle. Pour cette raison, il est important de comprendre quand vous devez utiliser le multitraitement, qui dépend des tâches que vous effectuez.

Pour vous en convaincre, regardons un exemple simple :

from multiprocessing import Pool
import time
import math

N = 5000000

def cube(x):
    return math.sqrt(x)

if __name__ == "__main__":
    
    start_time = time.perf_counter()
    with Pool() as pool:
      result = pool.map(cube, range(10,N))
    finish_time = time.perf_counter()
    print("Program finished in {} seconds - using multiprocessing".format(finish_time-start_time))
    print("---")
    
    start_time = time.perf_counter()
    result = []
    for x in range(10,N):
      result.append(cube(x))
    finish_time = time.perf_counter()
    print("Program finished in {} seconds".format(finish_time-start_time))

Cet extrait est basé sur l’exemple précédent. Nous résolvons le même problème, qui consiste à calculer la racine carrée de N chiffres, mais de deux manières. Le premier implique l’utilisation du multitraitement Python, tandis que le second ne le fait pas. Nous utilisons le perf_counter() méthode de la time bibliothèque pour mesurer les performances temporelles.

Sur mon portable, j’obtiens ce résultat :

> python code.py
Program finished in 1.6385094 seconds - using multiprocessing
---
Program finished in 2.7373942999999996 seconds

Comme vous pouvez le voir, il y a plus d’une seconde de différence. Donc dans ce cas, le multitraitement est préférable.

Modifions quelque chose dans le code, comme la valeur de N. Baissons-le à N=10000 et voyez ce qui se passe.

Voici ce que j’obtiens maintenant :

> python code.py
Program finished in 0.3756742 seconds - using multiprocessing
---
Program finished in 0.005098400000000003 seconds

Qu’est-il arrivé? Il semble que le multitraitement soit maintenant un mauvais choix. Pourquoi?

La surcharge introduite par le fractionnement des calculs entre les processus est trop importante par rapport à la tâche résolue. Vous pouvez voir combien il y a de différence en termes de performances temporelles.

Conclusion

Dans cet article, nous avons parlé de l’optimisation des performances du code Python en utilisant le multitraitement Python.

Tout d’abord, nous avons brièvement présenté ce qu’est le calcul parallèle et les principaux modèles pour l’utiliser. Ensuite, nous avons commencé à parler du multitraitement et de ses avantages. Au final, nous avons vu que paralléliser les calculs n’est pas toujours le meilleur choix et la multiprocessing module doit être utilisé pour paralléliser les tâches liées au processeur. Comme toujours, il s’agit de considérer le problème spécifique auquel vous êtes confronté et d’évaluer les avantages et les inconvénients des différentes solutions.

J’espère que vous avez trouvé l’apprentissage du multitraitement Python aussi utile que moi.




Source link