Pessimisme à propos du multithreading

La concurrence massive et matérielle est un sujet brûlant du 21e siècle. Il y a plusieurs bonnes raisons à cela, et une plutôt triste.

Deux bonnes raisons: une combinaison d'excellentes performances GPU dans les jeux et en même temps leur utilisation inattendue dans l'apprentissage en profondeur de l'IA, car un parallélisme massif y est implémenté au niveau matériel. La triste raison est que la vitesse des systèmes à processeur unique est contraire aux lois de la physique depuis 2006. Les problèmes actuels de fuite et de dégradation thermique limitent fortement l'augmentation de la fréquence d'horloge, et la chute de tension classique se heurte désormais à de graves problèmes de bruit quantique.

En concurrence pour attirer l'attention du public, les fabricants de processeurs tentent de pousser de plus en plus de cœurs de processeur dans chaque puce, vantant les performances globales théoriques. Les efforts de convoyage et les méthodes d'exécution spéculative, qui utilisent le multithreading sous le capot, augmentent également rapidement, de sorte qu'un seul processeur visible par le programmeur peut traiter les instructions plus rapidement.

La vérité gênante est que bon nombre de nos tâches informatiques moins glamour ne peuvent tout simplement pas très bien utiliser le multithreading visible. Il y a plusieurs raisons à cela, qui ont des conséquences différentes pour le programmeur, et il y a beaucoup de confusion. Dans cet article, je veux clarifier quelque peu la situation.

Tout d'abord, vous devez comprendre clairement où le parallélisme matériel fonctionne le mieux et pourquoi. Examinons les calculs pour les graphiques, les réseaux de neurones, le traitement du signal et l'extraction de bitcoins. Il existe un schéma: les algorithmes de parallélisation fonctionnent mieux sur un équipement qui est (a) spécialement conçu pour les faire fonctionner; (b) ne peut rien faire d'autre!

Nous voyons également que l'entrée pour les algorithmes parallèles les plus réussis (tri, correspondance de chaînes, transformée de Fourier rapide, opérations matricielles, quantification inverse des images, etc.) semble assez similaire. En règle générale, ils ont une structure métrique et la différence entre les données «proches» et «distantes» est implicite, ce qui nous permet de les découper en parties, car la connexion entre les éléments distants est insignifiante.

En termes du dernier article sur la localité sémantique, nous pouvons dire que les méthodes parallèles sont principalement applicables lorsque les données ont une bonne localité. Et ils fonctionnent mieux sur des équipements qui ne prennent en charge que les connexions «à courte portée», comme la matrice systolique au cœur du GPU.

D'un autre côté, il est très difficile d'écrire un logiciel qui produit efficacement une telle section pour les données d'entrée avec une mauvaise localisation sur des ordinateurs à usage général (architecture von Neumann).

Par conséquent, nous pouvons formuler une heuristique simple: les chances d'utiliser le calcul parallèle sont inversement proportionnelles au degré de non-localité sémantique irréductible dans les données d'entrée.

Une autre limitation du calcul parallèle est que certains algorithmes importants ne peuvent pas du tout être parallélisés - même théoriquement. Lorsque j'ai abordé ce sujet pour la première fois sur mon blog, j'ai trouvé le terme «algorithme malade», où SICK signifie «Serial, Intrinscally - Cope, Kiddo!» Les exemples significatifs incluent: l'algorithme de Dijkstra pour trouver le chemin le plus court; détection de cycles dans les graphes dirigés (en utilisant 3-SAT dans les solveurs); recherche approfondie; calculer le nième membre de la chaîne de hachage cryptographique; optimisation du flux réseau ... et ce n'est pas une liste complète.

La mauvaise localisation des données d'entrée joue également un rôle ici, en particulier dans les contextes du graphique et de l'arborescence. Les chaînes de hachage cryptographiques ne peuvent pas être parallélisées, car les enregistrements sont calculés dans un ordre strict - c'est vraiment une règle importante pour protéger la chaîne contre la contrefaçon.

Et ici, le verrou entre en jeu: vous ne pouvez rien paralléliser pendant que l'algorithme SICK fonctionne.

Nous n'avons pas fini. Il existe au moins deux classes d'obstacles, et des obstacles très courants.

Premièrement, il n'y a pas d'outils nécessaires. La plupart des langues ne supportent rien d'autre qu'un mutex et des sémaphores. C'est pratique, les primitives sont faciles à implémenter, mais cette situation provoque de terribles explosions de complexité dans la tête: il est presque impossible de comprendre l'échelle de plus de quatre verrous en interaction.

Si vous êtes chanceux, vous obtiendrez un ensemble plus accommodant de primitives, telles que les canaux Go (aka Communicating Sequential Processes) ou le système de propriété / envoi / synchronisation de Rust. Mais en fait, nous ne savons pas quel est le langage «correct» des primitives pour l'implémentation du parallélisme sur l'architecture von Neumann. Peut-être qu'il n'y a même pas un seul ensemble correct de primitives. Peut-être que deux ou trois ensembles différents conviennent à différents problèmes, mais ils sont incommensurables en tant qu'unité et racine carrée de deux. À ce jour, en 2018, personne ne sait vraiment.

Et la dernière limitation, mais non moins importante, est le cerveau humain. Même sur un algorithme clair avec une bonne localisation des données et des outils efficaces, la programmation parallèle est tout simplement difficile pour les gens, même si l'algorithme est appliqué tout simplement. Notre cerveau ne modélise pas très bien les espaces d'états les plus simples des programmes purement séquentiels, et en particulier ceux parallèles.

Nous le savons car il existe de nombreuses preuves réelles que le débogage du code parallèle est plus que difficile. Cela est entravé par les conditions de concurrence, les blocages, les verrous auto-destructeurs, la corruption de données insidieuse en raison d'un ordre d'instructions légèrement dangereux.

Je pense que la compréhension de ces limites devient plus importante après l'effondrement de la loi de mise à l'échelle de Dennard . En raison de tous ces goulots d'étranglement dans la programmation, une partie des systèmes multicœurs exécutera toujours un logiciel qui n'est pas en mesure de charger l'équipement à 100% de la puissance de calcul. Si vous regardez de l'autre côté, nous avons un excès de fer pour les tâches en cours. Combien d'argent et d'efforts gaspillons-nous?

Les fabricants de processeurs veulent que vous surestimiez les avantages fonctionnels des nouvelles puces intelligentes avec encore plus de cœurs. Sinon, comment peuvent-ils lever des fonds pour couvrir des coûts de production gigantesques, tout en restant rentables? Le marketing fait de son mieux pour que vous ne vous demandiez jamais quelles tâches un tel multithreading est vraiment bénéfique.

Honnêtement, il y a de telles tâches. Les serveurs des centres de données qui traitent des centaines de milliers de transactions simultanées par seconde sont susceptibles de répartir assez bien la charge entre les cœurs. Smartphones ou systèmes embarqués également - dans les deux cas, des efforts importants sont déployés pour minimiser les coûts et la consommation d'énergie, ce qui rend difficile la mise en service d'une puissance excédentaire.

Mais pour les utilisateurs ordinaires d'ordinateurs de bureau et d'ordinateurs portables? De vagues doutes me tourmentent. Il est difficile de comprendre la situation ici, car l'augmentation réelle de la productivité provient d'autres facteurs, tels que la transition du disque dur au SSD. De telles réalisations sont facilement confondues avec l'effet d'accélération du processeur, si vous n'effectuez pas un profilage approfondi.

Voici les motifs de ces soupçons:

  1. L'informatique parallèle sérieuse sur les ordinateurs de bureau / portables se produit uniquement sur le GPU.
  2. Plus de deux cœurs dans un processeur sont généralement inutiles. Les systèmes d'exploitation peuvent distribuer les flux d'applications, mais les logiciels classiques ne peuvent pas utiliser la concurrence, et la plupart des utilisateurs parviennent rarement à lancer simultanément un grand nombre d'applications différentes qui consomment beaucoup de ressources CPU pour charger complètement leur équipement.
  3. Par conséquent, la plupart des systèmes quadricœurs ne font la plupart du temps rien d'autre que générer de la chaleur.

Parmi mes lecteurs, il y a beaucoup de gens qui sont susceptibles de pouvoir raisonnablement commenter cette hypothèse. Il est intéressant de voir ce qu'ils disent.

MISE À JOUR Le commentateur de G + a souligné un avantage intéressant des processeurs multicœurs: ils compilent du code très rapidement. Le code source de langages comme C a une bonne localité: ici, des unités bien séparées (fichiers source) sont compilées dans des fichiers objets, que l'éditeur de liens combine ensuite.

Source: https://habr.com/ru/post/fr434698/


All Articles