control-systems-and-automation
Comment déterminer les coûts de synchronisation des fils dans les systèmes d'exploitation multifils
Table of Contents
La compréhension des coûts de synchronisation des fils est essentielle pour optimiser les performances dans les systèmes d'exploitation multifiltres. Ces coûts influent directement sur l'efficacité des fils de coordination et d'accès aux ressources partagées, ce qui affecte la réactivité et le débit globaux du système. Les frais généraux de synchronisation peuvent avoir une incidence significative sur les performances dans les environnements informatiques parallèles, où la fusion de données provenant de processus multiples peut entraîner des coûts beaucoup plus élevés, souvent de deux ordres de grandeur ou plus, que le traitement des mêmes données sur un seul fil, principalement en raison des frais généraux supplémentaires des mécanismes de communication et de synchronisation interprocessus.
Qu'est-ce que la synchronisation des fils et pourquoi est-ce important?
La synchronisation des fils est définie comme un mécanisme qui garantit que deux processus ou threads simultanés ou plus n'exécutent pas simultanément un segment de programme particulier appelé section critique. Dans les applications multithreaded, la synchronisation empêche les conditions de course et assure la cohérence des données lorsque plusieurs threads accèdent à des ressources partagées.
Il y a deux coûts distincts de synchronisation. Premièrement, il y a le coût opérationnel de la gestion des moniteurs. Ce coût peut être important : l'acquisition et les essais pour les serrures sur le moniteur pour chaque méthode synchronisée et bloc peuvent imposer beaucoup de frais généraux. Comprendre ces coûts est crucial pour les développeurs travaillant sur des applications critiques de performance, en particulier ceux fonctionnant sur des systèmes multicore où les frais généraux de synchronisation peuvent devenir un goulot d'étranglement majeur.
Les codes filetés utilisent généralement des verrous pour coordonner l'accès aux données partagées. Dans de nombreux cas, la contestation des verrous réduit l'efficacité parallèle et nuit à l'évolutivité. Sans mesure et analyse appropriées, les développeurs peuvent introduire inconsciemment des goulets d'étranglement de synchronisation qui empêchent leurs applications de s'étendre efficacement sur les processeurs multicore modernes.
Facteurs fondamentaux influant sur les coûts de synchronisation
Plusieurs facteurs interconnectés influencent les coûts associés à la synchronisation des fils dans les systèmes d'exploitation multifiltres. Comprendre ces facteurs est essentiel pour mesurer et optimiser avec précision les performances de synchronisation.
Type de synchronisation Primitif
Les caractéristiques de performance des différents primitifs de synchronisation sont très différentes. Les Mutexes, les sémaphores, les spinlocks, les serrures de lecture-écriture et les variables de condition ont des profils de surcoût uniques. Certaines applications du monde réel peuvent voir plus d'avantages en minimisant le temps de verrouillage d'une ressource plutôt que de choisir le meilleur primitif de synchronisation.
Les spinlocks, par exemple, consomment des cycles CPU en attendant la disponibilité des verrous, ce qui les rend adaptés à de courtes sections critiques mais gaspillent les attentes plus longues. Une autre façon efficace de mettre en œuvre la synchronisation est d'utiliser des spinlocks. Avant d'accéder à une ressource partagée ou à un code, chaque processeur vérifie un drapeau. Si le drapeau est réinitialisé, le processeur définit le drapeau et continue à exécuter le fil. Mais si le drapeau est réglé (verrouillé), les fils continueraient à tourner dans une boucle et continueraient à vérifier si le drapeau est réglé ou non. Inversement, bloquer les verrous qui mettent des fils pour dormir implique un changement de contexte au-dessus mais ne gaspillez pas les cycles CPU pendant les attente.
Verrouiller les niveaux de teneur
La dispute de verrouillage se produit lorsque plusieurs threads tentent d'acquérir le même verrou simultanément. La synchronisation sérialise l'exécution d'un ensemble d'instructions de sorte qu'un seul thread à la fois exécute ce jeu. Chaque fois que plusieurs threads essaient simultanément d'exécuter le même bloc synchronisé, ces threads sont effectivement exécutés ensemble en un seul thread. Cela annule complètement le but d'avoir plusieurs threads et est potentiellement un énorme goulot d'étranglement dans n'importe quel programme.
Certaines applications connaissent des pics de discorde sporadiques, tandis que d'autres sont confrontées à des arguments persistants qui limitent sévèrement l'évolutivité. La colonne « Changed. » indique à quelle fréquence un mutex spécifique change le fil de la propriété. Si le nombre est élevé, cela signifie que le risque de discorde est également élevé. La mesure de ces motifs aide les développeurs à comprendre si la discorde est un problème systémique ou une anomalie occasionnelle.
Considérations relatives à l'architecture matérielle
L'architecture matérielle sous-jacente joue un rôle critique dans les coûts de synchronisation. La capacité clé que nous avons besoin pour mettre en œuvre la synchronisation dans un multiprocesseur est un ensemble de primitifs matériels avec la capacité de lire et de modifier atomiquement un emplacement de mémoire. Sans une telle capacité, le coût de construction de primitifs de synchronisation de base sera trop élevé.
Les protocoles de cohérence des caches ont également une incidence importante sur les performances de synchronisation. Lorsque plusieurs cœurs accèdent aux mêmes variables de synchronisation, le rebondissement de la ligne de cache se produit comme transfert de propriété entre les cœurs. Ce trafic de cohérence des caches ajoute des frais généraux importants, en particulier sur les architectures NUMA (Non-Uniform Memory Access) où les latences d'accès à la mémoire varient en fonction de l'emplacement physique.
Durée critique de la section
Pour les méthodes courtes, l'utilisation d'une méthode synchronisée peut signifier que le temps de base nécessaire à l'appel de la méthode est beaucoup plus long que le temps nécessaire à son exécution. Le coût de l'appel d'une méthode non synchronisée peut être beaucoup plus faible que celui de l'appel d'une méthode synchronisée. Lorsque les sections critiques sont très courtes, le coût de l'appel d'une méthode non synchronisée peut noyer le travail réel protégé.
Cependant, un verrouillage à grains trop fins pour réduire la durée de la section critique peut introduire ses propres frais généraux par l'augmentation de la fréquence d'acquisition des verrous. Trouver l'équilibre optimal nécessite une mesure et une analyse minutieuses de la charge de travail spécifique de l'application.
Calendrier des fils et changement de contexte
Cette variation est générée par la nature du changement de contexte multithreaded, ainsi que par le fait que l'activité prenant une grande partie du temps dans ce test est la gestion des verrous. Le changement de contexte est essentiellement imprévisible, et la quantité de commutation et où il se produit affecte la fréquence à laquelle le VM doit libérer et réacquer les verrous dans différents fils.
En utilisant les deux techniques, je reçois des résultats assez similaires : quelque part entre 1,2 et 1,5 microsecondes par commutateur de contexte, ne prenant en compte que le coût direct, et le pinning à un seul noyau pour éviter les coûts de migration. Sans le pinning, le temps de commutation va jusqu'à ~2,2 microsecondes. Ces microsecondes se complètent rapidement dans les applications avec des conflits de verrouillage fréquents, faisant du changement de contexte une composante importante des coûts de synchronisation globale.
Méthodes complètes de mesure des coûts de synchronisation
Pour mesurer avec précision les coûts de synchronisation des fils, il faut combiner des outils, des techniques et des méthodologies. Différentes approches fournissent des informations complémentaires sur le comportement de synchronisation et l'impact de performance.
Outils de profilage et analyseurs de performance
Les outils de profilage modernes offrent des capacités sophistiquées pour analyser les frais généraux de synchronisation. Les outils de performance de Visual Studio 2010 comprennent une nouvelle méthode de profilage – profilage des litiges de ressources – qui vous aide à détecter les problèmes de concurrence entre les threads. Dans cet article, je passe par une enquête de profilage des litiges et explique les données qui peuvent être recueillies à l'aide des outils IDE 2010 de Visual Studio et de ligne de commande.
Pour chaque argument, le profileur signale le fil bloqué, où la dispute s'est produite (la pile de ressources et d'appels), quand la dispute s'est produite (la date) et la durée (la longueur) du fil bloqué en essayant d'acquérir un verrou, entrez une section critique, attendez un seul objet, etc. Cette information détaillée permet aux développeurs de repérer des goulets d'étranglement spécifiques de synchronisation et de comprendre leur impact sur les performances globales de l'application.
Pour les systèmes Linux, des outils comme perf fournissent une analyse de la rupture de verrouillage au niveau du noyau. Le comportement par défaut de l'outil recueille la statistique de la rupture par trace de pile (dans le noyau seulement) et affiche la fonction clé pour chaque entrée. De plus, des outils spécialisés comme mutrace[ offrent des capacités de profilage légères mutex. Pour améliorer la situation si vous avez maintenant écrit un profileur mutex appelé mutrace. Contrairement à valgrind/drd, il ne virtualise pas l'instruction CPU, ce qui le rend beaucoup plus rapide. En fait, les opérations de mutrace de hooks ne devraient que réduire au minimum l'influence sur l'exécution des applications. mutrace n'est pas utile pour trouver des bogues de synchronisation, il est uniquement utile pour le profilage des verrous.
Compteurs de performance matérielle
Les compteurs de performance du matériel permettent un accès à faible overhead aux mesures détaillées de niveau CPU liées à la synchronisation. Ces compteurs peuvent suivre les pannes de cache, les transactions de bus mémoire et les opérations atomiques – tous les indicateurs critiques de la synchronisation des frais généraux.
Les compteurs de performance sont particulièrement utiles pour comprendre les coûts de cohérence du cache associés à la synchronisation. Ils peuvent révéler les modèles de rebondissement de la ligne de cache, mesurer la fréquence des opérations atomiques et quantifier la bande passante de la mémoire consommée par le trafic de synchronisation.
Sections critiques du calendrier
Le timing direct des sections critiques permet de mesurer directement les frais généraux de synchronisation. La sortie de l'exécution de cette application montre que nous obtenons un peu moins de 700 incréments toutes les 5 secondes. Nous utiliserons cette mesure pour voir ce que sont les frais généraux des mécanismes de synchronisation des fils.
Les développeurs peuvent mettre en œuvre des instruments de chronométrage personnalisés en utilisant des minuteurs haute résolution pour mesurer la latence d'acquisition des verrous et les temps de maintien. En comparant les temps d'exécution avec et sans synchronisation, les frais généraux purs des mécanismes de synchronisation deviennent apparents.
Techniques d'analyse de la teneur en verrouillage
Enfin, nous proposons une nouvelle technique de mesure et d'analyse de l'affirmation de verrouillage qui utilise les données associées aux serrures pour blâmer les porte-fermets pour l'oisiveté des fils de rotation. Notre approche entraîne des frais généraux ≤ 5% sur une application de chimie quantique qui fait un usage étendu de verrouillage (fermetures distinctes de 65M, un maximum de serrures vivantes de 340K, et une moyenne de 30K d'acquisitions de serrures par seconde par fil) et attribue l'affirmation de verrouillage à ses contextes d'appel statique et dynamique. Notre stratégie, mise en œuvre dans HPCToolkit, est entièrement distribuée et devrait bien évoluer vers les systèmes avec des comptes de cœur importants.
En mode profilage de la teneur en ressources, le profileur ne recueille des données que pour les événements de synchronisation qui causent des assertions et ne déclarent pas des acquisitions réussies de ressources (non bloquées). Si votre application ne provoque pas de assertions, aucune donnée ne sera recueillie. Si vous obtenez des données, cela signifie que votre application a des assertions de verrouillage.
Contrôle du compteur de performance
Les systèmes d'exploitation exposent les compteurs de performance qui suivent les mesures liées à la synchronisation. Ce compteur montre les assertions de verrouillage comptent par seconde. Le problème est que chaque assertion de verrouillage est considérée comme 1, peu importe si le fil a attendu une nanoseconde ou une minute. Néanmoins, un grand nombre de assertions est un mauvais signe et devrait être étudié. Ces compteurs fournissent une vue de haut niveau du comportement de synchronisation sans nécessiter d'instrumentation de code.
Sur Windows, des outils comme PerfMon permettent d'accéder aux compteurs .NET CLR LocksAndThreads. Dans les applications .NET Core 3+, vous pouvez maintenant utiliser un outil de ligne de commande multiplateforme appelé dotnet-counters. Ceci est une grande amélioration étant donné qu'il n'y avait pas de bon moyen de consommer les compteurs de perf sur Linux jusqu'à présent. Ces compteurs permettent une surveillance continue des paramètres de synchronisation dans les environnements de production avec un minimum de frais généraux.
Profil basé sur le FPR
La technologie Berkeley Packet Filter (BPF) permet un profilage efficace et au niveau du noyau des événements de synchronisation avec un minimum de frais généraux. L'utilisation de BPF pour l'analyse de la rupture de verrouillage est bonne pour le débogage en direct rapide car il serait plus efficace. Mais comme elle n'enregistre pas le résultat, chaque exécution peut rapporter différentes données selon les caractéristiques du système.
Les noyaux Linux modernes supportent le profilage de verrouillage basé sur BPF grâce à des outils intégrés au sous-système perf. Ces outils peuvent suivre les acquisitions de verrouillage, mesurer la discordance et attribuer les frais généraux à des chemins de code spécifiques, tout en maintenant les frais généraux bas adaptés aux environnements de production.
Interprétation des mesures des coûts de synchronisation
La collecte de mesures de synchronisation n'est qu'une première étape : interpréter correctement ces mesures est crucial pour prendre des décisions éclairées en matière d'optimisation. Comprendre ce que signifient les chiffres et leur rapport avec la performance de l'application nécessite une analyse minutieuse.
Identification de la teneur problématique en verrou
Les symptômes classiques de graduation se produisent lorsque l'exécution d'une application sur un système avec un grand nombre de processeurs, de cœurs de processeur ou de fils matériels ne montre pas une échelle prévue dans le débit de performance par rapport à un système avec un petit nombre de processeurs, de cœurs de processeur ou de fils matériels, ou laisse l'utilisation de CPU inutilisée. En d'autres termes, si une application ne montre pas de problèmes de graduation, alors il n'est pas nécessaire d'étudier l'activité de verrouillage d'une application.
Mais seulement 8% utilisation CPU est rapporté en raison de la forte dispute de verrouillage. Oracle Solaris mpstat signale également un grand nombre de commutateurs de contexte de fil volontaire. Par conséquent, une application qui éprouve une forte dispute de verrouillage montre également un grand nombre de commutateurs de contexte volontaire. En bref, cette application présente des symptômes de la dispute de verrouillage.
Analyse des distributions de temps d'attente
Les délais d'attente ne sont pas tous également problématiques. Comprendre la répartition des temps d'attente aide à prioriser les efforts d'optimisation. Quelques très longues attentes peuvent indiquer différents problèmes que de nombreuses attentes courtes.
L'examen des distributions des temps d'attente révèle si la discorde est répartie uniformément ou concentrée dans des chemins de code spécifiques. Les temps d'attente très variables peuvent indiquer des modèles de charge de travail effrénés ou des problèmes d'inversion prioritaire.
Attribuer des voies de surenchères aux codes
Il est essentiel de comprendre quels chemins de code contribuent le plus à la synchronisation pour optimiser efficacement les frais généraux. Premièrement, nous 'blame' verrouillons la dispute sur le contexte du thread offensif plutôt que d'agréger le temps d'attente à un objet de synchronisation; cela dirige un analyste vers la source du problème.
Le profilage de la pile d'appel combiné avec les données de blocage révèle les contextes d'exécution responsables de la synchronisation des frais généraux. Cette information montre non seulement quels verrous sont revendiqués, mais quelles fonctionnalités d'application ou flux de travail déclenchent cette dispute. Comprendre ces relations permet des optimisations ciblées qui traitent les causes racine plutôt que les symptômes.
Stratégies avancées pour réduire au minimum les coûts de synchronisation
Une fois les coûts de synchronisation mesurés et compris, diverses stratégies peuvent réduire leur impact sur le rendement de l'application. L'approche la plus efficace dépend des modèles de contestation et des exigences de l'application.
Réduction de la portée et de la granularité des écluses
Réduire la portée des verrous – tant en termes de couverture de code que de protection des données – réduit les possibilités de contestation. Le verrouillage à grain fin protège les structures de données plus petites, permettant un parallélisme plus grand mais potentiellement augmentant les frais généraux de gestion des verrous.
Les mesures devraient guider les décisions concernant le fractionnement ou la consolidation des verrous. Dans certains cas, les données de restructuration permettant des verrous plus indépendants peuvent réduire considérablement les disputes sans que la gestion des verrous ne soit excessive.
Mise en œuvre des structures de données sans verrouillage
Les structures de données sans verrouillage utilisent des opérations atomiques au lieu de verrous pour coordonner l'accès simultané. Ces structures peuvent éliminer la contestation de verrouillage entièrement pour certains modèles d'accès. Les implémentations sans verrouillage communes comprennent des files d'attente, des piles et des tables de hachage qui utilisent des opérations de comparaison et de transfert pour maintenir la cohérence sans blocage.
En outre, la taille de l'échantillon est limitée à quatre mécanismes de synchronisation, excluant d'autres méthodes potentielles telles que les structures de données sans verrou ou la mémoire transactionnelle logicielle. Une mesure attentive est nécessaire pour vérifier que les approches sans verrou améliorent réellement les performances pour des charges de travail spécifiques.
Sélection de priorités de synchronisation appropriées
Différents primitifs de synchronisation ont différentes caractéristiques de performance. Mais dans un cas où vous pouvez choisir entre différentes approches de synchronisation de fil, choisir une méthode plus rapide au lieu d'un lent peut vous donner des avantages assez agréables. En particulier, il est important de savoir quand choisir les opérations interlockées sur un moniteur plein-blown.
Les verrous de lecture-écriture peuvent améliorer les performances lorsque l'on lit beaucoup plus de nombre d'écritures, permettant à plusieurs lecteurs concomitants tout en protégeant contre les modifications simultanées. Les sémaphores permettent le regroupement contrôlé des ressources.
Éviter l'exécution en série
Sur les machines avec plusieurs processeurs, vous pouvez laisser tous sauf un CPU au ralenti lorsque l'exécution sérialisée se produit. La refonte des algorithmes pour réduire ou éliminer les points de sérialisation peut améliorer considérablement l'évolutivité. Les techniques comprennent la partition des données pour permettre un traitement indépendant, en utilisant le stockage local de thread pour éviter le partage, et en utilisant des planificateurs de vol de travail qui minimisent la synchronisation.
Une façon d'éviter complètement l'exigence de synchroniser les méthodes est d'utiliser des objets et des structures de stockage séparés pour différents threads. Cette approche, parfois appelée confinement des threads, élimine entièrement les frais généraux de synchronisation en assurant que les données ne soient jamais partagées.
Optimiser la durée de la section critique
La réduction des verrous de temps diminue à la fois la probabilité de dispute et le temps d'attente quand la dispute se produit. Cela peut impliquer le déplacement de travaux non critiques à l'extérieur des blocs synchronisés, des valeurs de pré-computation avant l'acquisition des verrous, ou le report d'opérations coûteuses jusqu'à ce que les verrous soient libérés.
Cependant, une réduction excessive de la section critique peut faire reculer la fréquence d'acquisition des verrous ou exiger des modèles de synchronisation plus complexes. L'objectif est de maintenir les verrous aussi longtemps que nécessaire pour maintenir l'exactitude, mais pas plus courte si cela introduit d'autres frais généraux ou complexité.
Synchronisation assistée par le matériel
Ces éléments primitifs sont les éléments de base qui sont utilisés pour construire une grande variété d'opérations de synchronisation au niveau de l'utilisateur, y compris des choses telles que les serrures et les barrières. En général, les architectes ne s'attendent pas à ce que les utilisateurs utilisent les éléments primitifs de base, mais s'attendent plutôt à ce que les éléments primitifs soient utilisés par les programmeurs système pour construire une bibliothèque de synchronisation, un processus souvent complexe et difficile.
De nombreux éléments modernes du matériel fournissent de telles instructions atomiques, deux exemples communs étant: test-and-set, qui fonctionne sur un seul mot mémoire, et compare-and-swap, qui échange le contenu de deux mots mémoire. L'utilisation de ces primitifs matériels peut efficacement réduire considérablement les frais de synchronisation par rapport aux approches logicielles seulement.
Considérations relatives à la synchronisation entre les plates-formes et les entités spécifiques
Différents systèmes d'exploitation et plates-formes mettent en œuvre des primitives de synchronisation différemment, ce qui conduit à des caractéristiques de performance variables.
Mécanismes de synchronisation Linux
Dans l'obscurité, les âges antérieurs à la version 2.6, le noyau Linux n'avait pas beaucoup de support spécifique pour les threads, et ils étaient plus ou moins piratés en plus du support du processus. Avant les futex, il n'y avait pas de solution dédiée de synchronisation à faible latence (elle était faite à l'aide de signaux); ni il n'y avait beaucoup d'utilisation des capacités des systèmes multi-cœurs.
Le mécanisme de l'espace utilisateur rapide mutex de Linux minimise l'implication du noyau pour les verrous non confinés, fournissant une excellente performance pour les cas courants. Ce n'est que lorsque la dispute se produit que le noyau devient impliqué pour gérer le blocage et le réveil des threads.
Primitifs de synchronisation Windows
Windows fournit un riche ensemble de primitives de synchronisation, y compris les sections critiques, mutexes, sémaphores, et les événements. Les sections critiques sont optimisées pour la synchronisation intra-processus et utilisent des stratégies spin-then-attendent pour minimiser les frais généraux.
Windows fournit également des verrous de lecteur/écriture minces et des variables de condition qui offrent des performances améliorées pour des scénarios spécifiques. Comprendre quand utiliser chaque type primitif est crucial pour des performances optimales sur les plates-formes Windows. L'exécution .NET ajoute une autre couche d'abstractions de synchronisation que les développeurs doivent comprendre et mesurer.
Impacts de l'architecture de la NUMA
Les versions monocore et multicore du simulateur VCS de Synopsys ont été utilisées pour ces mesures sur une machine Intel octacore avec une RAM de 8 Go dans l'architecture de l'accès à la mémoire non uniforme (NUMA). Comme le montre le tableau 1, une application simple de simulation multicore exploite le parallélisme de la conception dans une certaine mesure, mais la vitesse n'est pas aussi élevée (1,36 et 1,46 pour 2 et 3 carottes respectivement).
Sur les systèmes NUMA, les variables de synchronisation devraient idéalement être réparties en mémoire à proximité des threads qui y accèdent le plus fréquemment. La synchronisation des nœuds croisés entraîne une latence plus élevée que la synchronisation intra-noeud. Les stratégies de placement des fils et d'allocation de mémoire influent de façon significative sur les performances de synchronisation des architectures NUMA.
Études de cas et exemples pratiques dans le monde réel
L'examen d'exemples concrets d'analyse et d'optimisation des coûts de synchronisation fournit des informations précieuses sur l'application pratique des techniques de mesure et des stratégies d'optimisation.
Scénarios à forte concentration
Cette ligne confirme non seulement que l'ajout de tâches à une file centralisée est problématique, mais quanti-fie l'impact. Les files d'attente centralisées représentent une source commune de conflit de verrouillage dans les applications multithreaded. Lorsque tous les threads sont en concurrence pour accéder à une file unique, la dispute devient grave à mesure que le nombre de threads augmente.
Le profilage a révélé que (67,5 % du total des inactif) découle de la création de Futures. Une approche utilisant des files d'attente distribuées et le vol de travail réduirait probablement considérablement la discordance des verrous.
Mesure de l'impact de l'optimisation
Le fait de mettre un moniteur autour de votre opérateur d'accroissement ralentira votre application à près de 1/20ème de la vitesse. Bien sûr, le blocage relatif des frais généraux se rétrécira lorsque votre fonctionnement verrouillé devient plus lourd, de sorte que la plupart des scénarios pratiques ne verront pas de différences aussi dramatiques entre les différents modèles.
Pour les opérations insignifiantes, les frais généraux de synchronisation dominent. Pour des travaux plus substantiels, la synchronisation devient une fraction plus petite du coût total. Cette relation guide les décisions quant à savoir quand optimiser la synchronisation par rapport à quand se concentrer sur d'autres aspects de performance.
Compilateur et Optimisations de l'exécution
Les travaux antérieurs ont montré que les frais généraux à haute performance de RMT proviennent non seulement de l'exécution de threads redondants, mais aussi des frais généraux de synchronisation entre les threads originaux et redondants. Les frais généraux de synchronisation entre les threads peuvent être particulièrement importants si la synchronisation est mise en œuvre en utilisant la mémoire globale.
Les compilateurs et les runtimes modernes utilisent diverses optimisations pour réduire les frais généraux de synchronisation. D'autre part, je ne devrais pas sous-estimer le fait que les derniers 1.3 et 1.4 VMs font tous très bien pour minimiser les frais généraux de synchronisation (surtout le mode serveur 1.4), si bien que les frais généraux de synchronisation ne devraient pas être un problème pour la plupart des applications.
Meilleures pratiques pour la synchronisation Gestion des coûts
Une gestion efficace des coûts de synchronisation nécessite une approche systématique combinant la mesure, l'analyse et l'optimisation.
Établir des niveaux de référence en matière de rendement
Avant de tenter d'optimiser, établir des niveaux de référence de performance clairs qui quantifient les coûts de synchronisation actuels. Mesurer les paramètres clés, y compris les taux de blocage, les temps d'attente, l'utilisation du processeur et le débit sous des charges de travail représentatives.
Les mesures de base devraient porter sur divers scénarios, notamment le comptage des fils, l'intensité de la charge de travail et la taille des données, ce qui montre comment les coûts de synchronisation s'évaluent avec les paramètres du système, ce qui aide à déterminer les conditions dans lesquelles les problèmes deviennent graves.
Profil avant d'optimiser
La stratégie principale pour résoudre les problèmes de performance, et pas seulement les assertions de verrouillage, que je recommande est assez simple : Commencez par le profilage de performance en mode d'échantillonnage si possible. Cela montre habituellement le problème juste là. Si vous ne trouvez pas le problème avec le profilage ou ce n'est pas possible pour quelque raison que ce soit, je regarde les compteurs de performance, en vérifiant : % Temps du processeur, % Temps en GC, Exception taux/sec, I/O lire octets, et Lock taux/sec. L'optimisation basée sur les données basées sur les mesures réelles empêche les efforts gaspillés sur les non-questions.
Le profilage révèle quels verrous sont en fait problématiques plutôt que quels verrous les développeurs supposent sont problématiques. Ces données objectives concentrent les efforts d'optimisation sur les opportunités les plus impact. Sans profilage, les développeurs risquent d'optimiser le code qui n'affecte pas significativement les performances globales.
Maintenir la sécurité des fils
Cela dit, ne pensez même pas à sauter sur la sécurité des fils si votre application a réellement un scénario multi-threading. Tout problème de corruption de données que vous pouvez faire face sont extrêmement dangereux et notoirement complexe à déboguer. Bien que l'optimisation des coûts de synchronisation est importante, la justesse ne doit jamais être compromise. Toutes les optimisations doivent préserver les garanties de sécurité des fils.
Les tests approfondis sous charge simultanée sont essentiels pour modifier la logique de synchronisation. Les conditions de course et d'autres bogues de proximité peuvent être subtils et difficiles à reproduire. Les outils de test automatisés et les tests de stress aident à vérifier que les optimisations n'introduisent pas de problèmes de correction.
Considérer les caractéristiques de la charge de travail
Les stratégies optimales de synchronisation dépendent fortement des caractéristiques de la charge de travail. Les charges de travail lourdes en lecture bénéficient de différentes approches que les charges de travail lourdes en écriture.
L'analyse de la charge de travail devrait examiner les modèles d'accès, les modèles de partage de données et les caractéristiques temporelles. Cette information révèle des possibilités d'optimisations comme les verrous de lecture-écriture, le partitionnement ou le lotage qui s'alignent sur le comportement réel de l'application.
Surveiller les performances de production
Le comportement de synchronisation dans les environnements de production diffère souvent des environnements de développement ou de test en raison de charges de travail, de volumes de données et de niveaux de concordance différents.
Les outils de surveillance à faible niveau de couverture permettent une observation continue sans avoir d'incidence significative sur la performance de production.
Tendances et orientations futures
Le paysage de la synchronisation des fils continue d'évoluer à mesure que les architectures matérielles avancent et que de nouveaux modèles de programmation émergent.
Mémoire transactionnelle
Les systèmes de mémoire transactionnelle logicielle et matérielle offrent d'autres approches de synchronisation qui peuvent simplifier la programmation tout en réduisant potentiellement les frais généraux. Ces systèmes permettent aux développeurs de spécifier des régions atomiques sans verrouillage explicite, avec la détection et la résolution de conflits de gestion d'exécution.
Nombres de centres supérieurs accrus
Les algorithmes et les structures de données qui s'étendent bien à des dizaines ou des centaines de carottes exigent une attention particulière aux coûts de synchronisation. Les systèmes futurs exigeront des approches encore plus sophistiquées pour minimiser les disputes et maximiser le parallélisme.
Calculs hétérogéniques
Les systèmes hétérogéniques combinant processeurs, GPU et accélérateurs spécialisés présentent de nouveaux défis de synchronisation. La coordination des travaux entre différents éléments de traitement avec différentes hiérarchies de mémoire et les primitives de synchronisation nécessite de nouvelles techniques de mesure et d'optimisation.
Optimisation assistée par la machine
Les recherches émergentes explorent l'utilisation de l'apprentissage automatique pour identifier et optimiser automatiquement les goulets d'étranglement de synchronisation. Ces systèmes analysent les données de profilage pour suggérer des transformations de code ou des ajustements de paramètres qui réduisent les frais généraux de synchronisation.
Outils et ressources pratiques
De nombreux outils et ressources sont disponibles pour aider les développeurs à mesurer et optimiser les coûts de synchronisation des fils. La familiarité avec ces outils permet une analyse et une optimisation efficaces des performances.
Outils de profilage Open Source
L'outil Linux perf fournit des capacités d'analyse de performance complètes, y compris le profilage des assertions de verrouillage. Valgrind avec l'outil DRD (Data Race Detector) peut identifier des problèmes de synchronisation, bien que Sur le drd de Linux valgrind puisse être utilisé pour suivre la discorde mutex. Malheureusement, exécuter des applications sous valgrind/drd ralentit massivement, ayant souvent l'effet de générer plusieurs des assertions que l'on essaie de suivre.
Pour les applications Java, des outils comme JConsole et VisualVM fournissent des capacités de surveillance des verrous intégrées. L'analyseur de verrouillage d'IBM pour Java calcule une métrique qui reflète le nombre d'acquisitions de verrous retardés en pourcentage du total des acquisitions de verrous.
Profileurs commerciaux
Intel VTune Profiler fournit une analyse détaillée des frais de synchronisation sur les processeurs Intel. JetBrains dotTrace et RedGate ANTS Performance Profiler offrent un profilage .NET complet, y compris une analyse des problèmes de verrouillage. Ces outils offrent souvent des capacités de visualisation et d'analyse plus sophistiquées que les solutions de rechange open-source.
Documentation et ressources pédagogiques
La compréhension de la synchronisation nécessite une base solide dans les principes de programmation concurrente. Des ressources comme "L'Art de la programmation multiprocesseur" de Maurice Herlihy et Nir Shavit fournissent une couverture complète de la théorie et de la pratique de la synchronisation.
Les communautés et forums en ligne offrent des conseils pratiques et une aide au dépannage. Le trop-plein d'attente, les communautés de programmation de Reddit et les forums spécialisés pour des plateformes spécifiques offrent des idées précieuses de développeurs expérimentés qui ont résolu des défis similaires de synchronisation.
Pour plus d'informations sur l'optimisation des performances et la programmation simultanée, envisagez d'explorer les ressources de La documentation du noyau Linux sur le verrouillage[, La documentation de threading[ et Le tutoriel Java de CONFURNENCE d'Oracle.
Conclusion
La détermination des coûts de synchronisation des fils dans les systèmes d'exploitation multifiltres est une compétence essentielle pour développer des applications concurrentes à haute performance. Grâce à une mesure systématique utilisant des outils de profilage, des compteurs de performance et des techniques d'analyse spécialisées, les développeurs peuvent identifier les goulets d'étranglement de synchronisation et quantifier leur impact sur les performances de l'application.
En établissant des niveaux de référence de performance, en établissant le comportement réel et en appliquant des stratégies d'optimisation appropriées, les développeurs peuvent minimiser les frais généraux de synchronisation tout en maintenant la justesse. Comme les systèmes continuent à s'étendre à des comptes de base plus élevés et des architectures plus complexes, l'importance de comprendre et d'optimiser les coûts de synchronisation ne fera qu'augmenter.
Les outils et techniques discutés dans cet article fournissent une base complète pour l'analyse et l'optimisation de la synchronisation des fils dans les systèmes multifiltres modernes. Que ce soit avec Linux, Windows ou d'autres plateformes, les principes de mesure et d'optimisation restent cohérents. En appliquant ces pratiques systématiquement, les développeurs peuvent construire des applications concurrentes évolutives et hautes performances qui utilisent efficacement des matériels multicore modernes.