Dans les programmes simultanés, les programmeurs accorderont une attention particulière à la synchronisation des données entre différents processus ou threads. Surtout lorsque plusieurs threads modifient la même variable en même temps, une synchronisation fiable ou d'autres mesures doivent être prises pour garantir que les données sont modifiées correctement. . Ici, un principe important est le suivant : ne faites pas d'hypothèses sur l'ordre dans lequel les instructions sont exécutées. Vous ne pouvez pas prédire l'ordre dans lequel les instructions entre les différents threads seront exécutées.
Mais dans un programme monothread, il est généralement facile pour nous de supposer que les instructions sont exécutées séquentiellement, sinon vous pouvez imaginer les terribles changements qui arriveront au programme. Le modèle idéal est le suivant : l'ordre dans lequel les différentes instructions sont exécutées est unique et ordonné. Cet ordre est l'ordre dans lequel elles sont écrites dans le code, quel que soit le processeur ou d'autres facteurs. Ce modèle est appelé modèle de cohérence séquentielle. c'est un modèle basé sur le système de von Neumann. Bien entendu, cette hypothèse est raisonnable en soi et se produit rarement anormalement dans la pratique, mais en fait, aucune architecture multiprocesseur moderne n’adopte ce modèle car il est tout simplement trop inefficace. Dans l'optimisation de la compilation et le pipeline CPU, presque tous impliquent une réorganisation des instructions.
Réorganisation au moment de la compilation
Une réorganisation typique au moment de la compilation consiste à ajuster l'ordre des instructions pour réduire autant que possible le nombre de lectures et de stockages de registre sans modifier la sémantique du programme, et à répliquer entièrement la valeur stockée du registre.
Supposons que la première instruction calcule une valeur et l'attribue à la variable A et la stocke dans un registre. La deuxième instruction n'a rien à voir avec A mais doit occuper un registre (en supposant qu'elle occupera le registre où A). est localisé). La troisième instruction L'instruction utilise la valeur de A et est indépendante de la deuxième instruction. Alors si selon le modèle de cohérence séquentielle, A est mis dans le registre après l'exécution de la première instruction, A n'existe plus lorsque la deuxième instruction est exécutée, et A est de nouveau lu dans le registre lorsque la troisième instruction est exécutée, et pendant ce processus, la valeur de A n’a pas changé. Habituellement, le compilateur échange les positions des deuxième et troisième instructions, de sorte que A existe dans le registre à la fin de la première instruction, puis la valeur de A peut être lue directement à partir du registre, réduisant ainsi la surcharge de lecture répétée.
L'importance de la réorganisation du pipeline
Les processeurs modernes utilisent presque tous le mécanisme de pipeline pour accélérer le traitement des instructions. De manière générale, une instruction nécessite plusieurs cycles d'horloge CPU pour être traitée, et c'est le cas. exécutées en parallèle via le pipeline, plusieurs instructions peuvent être exécutées dans le même cycle d'horloge. La méthode spécifique consiste simplement à diviser les instructions en différents cycles d'exécution, tels que la lecture, l'adressage, l'analyse, l'exécution et d'autres étapes, et à les placer dans différentes étapes. composants à traiter. Dans le même temps, dans l'unité d'exécution EU, les unités fonctionnelles sont divisées en différents composants, tels que des composants d'addition, des composants de multiplication, des composants de chargement, des composants de stockage, etc., qui peuvent en outre réaliser une exécution parallèle de différents calculs. .
L'architecture du pipeline détermine que les instructions doivent être exécutées en parallèle, et non comme envisagé dans le modèle séquentiel. La réorganisation permet d'utiliser pleinement le pipeline, obtenant ainsi des effets superscalaires.
Assurer la séquence
Bien que les instructions ne soient pas nécessairement exécutées dans l'ordre dans lequel nous les avons écrites, il ne fait aucun doute que dans un environnement monothread, l'effet final de l'exécution des instructions devrait être le même que L'effet est cohérent en cas d'exécution séquentielle, sinon cette optimisation n'aura aucun sens.
Habituellement, les principes ci-dessus seront respectés, que la réorganisation des instructions soit effectuée au moment de la compilation ou au moment de l'exécution.
Réorganisation dans le modèle de stockage Java
Dans le modèle de mémoire Java (JMM), la réorganisation est une section très importante, en particulier dans la programmation simultanée. JMM garantit la sémantique d'exécution séquentielle via la règle « arrive avant ». Si vous souhaitez que le thread exécutant l'opération B observe les résultats du thread exécutant l'opération A, alors A et B doivent satisfaire au principe « arrive avant ». Sinon, la JVM peut effectuer des opérations arbitraires. opérations sur eux. Tri pour améliorer les performances du programme.
Le mot-clé volatile peut garantir la visibilité des variables, car les opérations sur volatile sont toutes dans la mémoire principale et la mémoire principale est partagée par tous les threads. Le prix ici est que les performances sont sacrifiées et que les registres ou les registres ne peuvent pas être utilisés. . Cache, car ils ne sont pas globaux, la visibilité ne peut pas être garantie et des lectures incorrectes peuvent se produire.
Une autre fonction de volatile est d'empêcher localement la réorganisation. Les instructions d'opération sur les variables volatiles ne seront pas réorganisées, car si elles sont réorganisées, des problèmes de visibilité peuvent survenir.
En termes d'assurance de la visibilité, les verrous (y compris les verrous explicites et les verrous d'objet) ainsi que la lecture et l'écriture de variables atomiques peuvent garantir la visibilité des variables. Cependant, les méthodes de mise en œuvre sont légèrement différentes. Par exemple, le verrouillage de synchronisation garantit que les données sont relues de la mémoire pour actualiser le cache lorsque le verrouillage est obtenu. Lorsque le verrouillage est libéré, les données sont réécrites dans la mémoire pour garantir. que les données sont visibles, tandis que les variables volatiles lisent et écrivent simplement la mémoire.
Pour une introduction plus détaillée à la réorganisation JVM en JAVA et des articles connexes, veuillez faire attention au site Web PHP chinois !