Développement d'un mécanisme de parallélisation du code python à l'aide de conteneurs docker

Le stade actuel de développement des technologies, y compris l'informatique, nous montre la croissance des volumes de données et le besoin d'ordinateurs de plus en plus puissants. Le développement des processeurs centraux a toujours été basé sur la technologie de l'augmentation du nombre de transistors sur une puce de microprocesseur. La loi bien connue de Moore dit: «Si cette tendance se poursuit, la puissance des appareils informatiques peut croître de façon exponentielle dans un laps de temps relativement court (24 mois).»



Cependant, le même Moore en 2003 a publié l'ouvrage «No Exponential is Forever: Mais« Forever »Can Be Delayed! », Dans lequel il a admis que la croissance exponentielle des quantités physiques pendant une longue période est impossible. Seule l'évolution des transistors et de leurs technologies de fabrication a permis d'étendre la loi à plusieurs générations supplémentaires.



En 2007, Moore a déclaré que la loi cesserait apparemment bientôt de fonctionner en raison de la nature atomique de la matière et de la vitesse de limitation de la lumière. Actuellement, la taille maximale d'un transistor dans un processeur est de 5 nanomètres. Il existe également des échantillons d'essai du processeur 3 nm, mais sa sortie ne débutera qu'en 2021. Cela suggère que bientôt l'augmentation supplémentaire du nombre de transistors sur une puce s'arrêtera (jusqu'à ce qu'un nouveau matériau soit découvert ou que le processus technologique soit radicalement mis à jour).



L'une des solutions à ce problème est le calcul parallèle. Ce terme est compris comme un moyen d'organiser les calculs informatiques dans lesquels les programmes sont développés comme un ensemble de processus de calcul interactifs qui fonctionnent en parallèle (simultanément).



Le calcul parallèle par la méthode de synchronisation est divisé en deux types.



Dans la première variante, l'interaction des processus se fait via la mémoire partagée: un thread d'exécution distinct est lancé sur chaque processeur d'un système multiprocesseur. Tous les threads appartiennent à un processus. Les threads échangent des données via une zone de mémoire partagée pour un processus donné. Le nombre de threads correspond au nombre de processeurs. Les flux sont créés soit au moyen d'un langage de programmation (par exemple, Java, C #, C ++ à partir de C ++ 11, C à partir de C11), soit à l'aide de bibliothèques. Dans ce cas, il est possible de créer des threads explicitement (par exemple, en C / C ++ en utilisant PThreads), de manière déclarative (par exemple, en utilisant la bibliothèque OpenMP), ou automatiquement - en utilisant les outils de compilation intégrés (par exemple, High Performance Fortran). La variante décrite de la programmation parallèle nécessite généralement une forme de capture de contrôle (mutex, sémaphores,moniteurs) pour coordonner les flux entre eux.



Dans la deuxième variante, l'interaction est réalisée par transmission de message. Un processus monothread est démarré sur chaque processeur dans un système multiprocesseur et communique avec d'autres processus s'exécutant sur d'autres processeurs à l'aide de messages. Les processus sont créés explicitement en appelant la fonction appropriée du système d'exploitation et les messages sont échangés à l'aide d'une bibliothèque spéciale (par exemple, l'implémentation du protocole MPI) ou à l'aide d'outils de langage (par exemple, High Performance Fortran, Erlang ou occam).



En plus des deux décrits ci-dessus, une option hybride est également utilisée: sur les systèmes multiprocesseurs avec mémoire distribuée (DM-MIMD), où chaque nœud du système est un multiprocesseur avec mémoire partagée (SM-MIMD), l'approche suivante peut être utilisée. Un processus multithread est lancé sur chaque nœud du système, qui distribue les threads entre les processeurs de ce nœud. L'échange de données entre les threads sur un nœud est effectué via la mémoire partagée et l'échange de données entre les nœuds - via le transfert de messages. Dans ce cas, le nombre de processus est déterminé par le nombre de nœuds et le nombre de threads est déterminé par le nombre de processeurs sur chaque nœud. La méthode hybride de programmation parallèle est plus compliquée (il est nécessaire de réécrire le programme parallèle d'une manière spéciale), mais elle est la plus efficace en utilisant les ressources matérielles de chaque nœud du système multiprocesseur.



Dans cet article, je propose d'adapter une telle approche hybride pour la parallélisation des calculs en Python. La principale caractéristique du travail est l'utilisation de la technologie des conteneurs docker. Le cadre en cours de développement aura une architecture client-serveur, qui comprend les éléments suivants.



Côté client:



  1. Sérialiseur: conformément au nom, il sérialise les fonctions et leurs variables (c'est-à-dire qu'il permet de les enregistrer sur un périphérique externe ou un réseau puis de les charger en mémoire sur le même ou un autre nœud). Il convient également de mettre en évidence le décorateur parallèle, qui est une fonction wrapper qui vous permet d'utiliser le sérialiseur pour des fonctions de différents types.
  2. Classes pour la configuration de la connexion serveur / cluster
  3. Outils de langage supplémentaires pour marquer les fonctions à paralléliser.


Du côté serveur:



  1. Désérialiseur - en conséquence, désérialise les données reçues (voir ci-dessus).
  2. Executor est une classe qui traite les données désérialisées (fonctions et leurs arguments), et installe également les bibliothèques nécessaires dans l'environnement virtuel de l'interpréteur Python.


L'architecture générale du système en cours de développement est représentée sur la figure.



image



Pour la communication entre le client et le serveur, des sockets ou le framework torsadé peuvent être utilisés, l'interaction avec laquelle sera effectuée via l'API développée.



La mise en œuvre de ce système suppose l'utilisation de la technologie docker. Cela vous permet d'offrir une configuration logicielle pratique et rapide pour commencer: démarrez simplement le cluster docker-swarm, déployez l'image docker sur le serveur sélectionné et définissez le nombre de réplications.



D'autres avantages importants de l'utilisation de la technologie docker sont la création d'un environnement informatique homogène en virtualisant un système de type UNIX (Ubuntu - léger Alpine Linux), ainsi que la présence d'un mode swarm, qui vous permet d'exécuter plusieurs conteneurs sur différents serveurs et d'équilibrer rapidement la charge en transférant des tâches vers des conteneurs libres ...



Le cadre développé peut être utilisé dans divers domaines où il est nécessaire d'effectuer de grandes quantités de calculs en langage Python, y compris pour les tâches d'apprentissage automatique et d'analyse de données approfondies, ainsi que pour des tâches plus simples, par exemple pour la vérification de décision distribuée lors de la programmation des olympiades.



All Articles