Multiprocessing et Multithreading : la prochaine étape pour améliorer les performances de votre code

Lorsque vous écrivez un programme informatique, l’efficacité est un facteur clé à prendre en compte. En cherchant à maximiser la performance de votre code, vous pouvez être amené à vous questionner sur les différentes techniques qui permettent de rendre un programme plus réactif ou rapide. Bien sûr, il en existe plusieurs. Parmi elles, deux sont largement répandues : le multiprocessing et le multithreading. Elles permettent à un programme d’exécuter plusieurs tâches en parallèle, augmentant ainsi son efficacité.

Abordons plus en détails leurs spécificités, et les cas de figure où leur utilisation est particulièrement intéressante.

Multiprocessing

Le multiprocessing, ou traitement parallèle, est une méthode qui permet l’exécution simultanée de plusieurs processus (ou programmes). Cette méthode est rendue possible grâce à l’utilisation de plusieurs processeurs, ou d’un processeur multicœur. Chaque processus est indépendant des autres et dispose de sa propre mémoire ainsi que de son propre espace d’adressage. Par conséquent, les processus ne partagent pas directement de données entre eux, bien qu’il existe certains mécanismes permettant une communication inter-processus.

Multithreading

Le multithreading fait référence à la capacité d’un processeur à exécuter simultanément plusieurs threads, ou fils d’exécution, au sein d’un même processus. Un thread est la plus petite unité d’exécution, et plusieurs threads au sein d’un même processus peuvent partager le même espace d’adressage. Ils peuvent ainsi accéder à la même mémoire et aux mêmes données. Cela peut mener à des problèmes de concurrence si plusieurs threads tentent d’accéder ou de modifier la même donnée simultanément. Heureusement, il existe des concepts qui permettent d’éviter ces problèmes comme les mutex (pour Mutual Exclusion) et les locks. Ces mécanismes peuvent être vu comme des verrous qui limitent l’accès à certaines sections de la mémoire à un seul thread à la fois. Cependant, les détails de ces concepts dépassent le cadre de cet article.

Comment choisir entre les deux ?

Le multiprocessing est une solution idéale pour les tâches qui sont intensives pour les processeurs, en particulier les tâches nécessitant une grande quantité de calculs. Ainsi, il n’est pas rare de trouver du multiprocessing dans les calculs et simulations scientifiques qui nécessitent des opérations lourdes. Le multiprocessing est également courant dans l’analyse de données, lorsqu’il est nécessaire de traiter de grands volumes de données. Enfin, le multiprocessing est aussi largement utilisé dans l’édition de vidéo.

Le multithreading est plus fréquemment utilisé pour des programmes qui attendent qu’un événement se produise, comme une action de l’utilisateur ou une réponse réseau. Ainsi, on retrouve souvent du multithreading dans les serveurs web, où il permet de gérer plusieurs connexions en parallèle. Dans le développement de jeux vidéo, le multithreading peut être utilisé pour séparer et gérer simultanément différents aspects du jeu, comme le rendu graphique, la logique de jeu et le traitement des entrées de l’utilisateur, ce qui contribue à une expérience de jeu plus fluide. Dans le développement d’interfaces graphiques, le multithreading est souvent utilisé pour maintenir une interface réactive, permettant à l’interface utilisateur de rester interactive pendant l’exécution de tâches de fond comme le chargement de données ou la réalisation de calculs intensifs.

Comment utiliser le multiprocessing et le multithreading ?

Nous allons maintenant voir un exemple d’utilisation de ces méthodes pour des tâches relativement simples. Pour se faire, nous utiliserons le langage C (pas besoin de me remercier (-: ) et les bibliothèques OpenMP pour le multithreading et MPI pour le multiprocessing.

OpenMP (pour Open Multi-Processing) utilise des directives comme #pragma omp pour indiquer au compilateur où et comment paralléliser le code. Ainsi, pour la multiplication de matrice, on peut paralléliser une boucle for avec #pragma omp parallel for :


#include <omp.h>
#define SIZE 1000
double A[SIZE][SIZE];
double B[SIZE][SIZE];
double C[SIZE][SIZE];
int main() {
// Initialize A and B
omp_set_num_threads(4);
// Parallélisation
#pragma omp parallel for
for(int i = 0; i < SIZE; i++) {
for(int j = 0; j < SIZE; j++) {
C[i][j] = 0.0;
for(int k = 0; k < SIZE; k++) {
C[i][j] += A[i][k] * B[k][j];
}
}
}
return 0;
}

view raw

code1.c

hosted with ❤ by GitHub

La fonction omp_set_num_threads permet de choisir le nombre de threads souhaité, ici 4. Pour compiler le code, nous utilisons GCC en ajoutant simplement l’option -fopenmp : gcc -fopenmp programme.c -o programme

MPI (pour Message Passing Interface) permet de lancer plusieurs fois le même programme en attribuant à chacun un identifiant (ou rang). Le nombre de processus à lancer est défini au moment de l’exécution. Ainsi, selon le rang du processus, le comportement peut être amené à changer. Dans le code suivant, chaque processus affiche un message indiquant son rang et le nombre total de processus.


#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(NULL, NULL);
// Récupération du nombre de processus
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
// Récupération de l'identifiant du processus
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
printf("Hello from process %d of %d\n", world_rank, world_size);
MPI_Finalize();
return 0;
}

view raw

code2.c

hosted with ❤ by GitHub

Pour compiler le code, un wrapper de GCC appelé mpicc est nécessaire : mpicc programme.c -o programme. Une fois le programme compilé, il est lancé avec la commande mpiexec en indiquant le nombre de processus avec l’option -np : mpiexec -np 4 ./programme.

Le multithreading et le multiprocessing dans d’autres langages

Nous avons pris le langage C comme exemple d’utilisation ici, mais ces concepts ne sont pas limités à ce langage. De nombreux autres langages de programmation modernes supportent ces techniques de parallélisation.

Pour donner quelques exemples :

  • La classe std::thread et MPI en C++
  • La classe Thread et la bibliothèque Akka en Java
  • Les modules threading et multiprocessing en Python

Il faut garder à l’esprit que chaque langage a ses propres spécificités et mécanismes pour gérer le multiprocessing et le multithreading, mais le but reste le même : exécuter plusieurs tâches simultanément pour améliorer les performances et l’efficacité de votre code.