En fait, les gens qui écrivent Java semblent n'avoir rien à voir avec le CPU. Tout au plus, cela a quelque chose à voir avec la façon d'exécuter le CPU et comment définir le nombre de threads que nous avons mentionnés plus tôt. Cependant, cet algorithme n'est qu'une référence. De nombreux scénarios différents nécessitent des moyens pratiques pour le résoudre. De plus, après avoir exécuté le CPU, nous considérerons également comment rendre le CPU pas si plein. Haha, les humains, c'est tout. Haha, ok, cet article concerne d'autres choses. Peut-être que vous n'écrivez presque pas de code dans Java. Faites attention au CPU, car la satisfaction de l'entreprise est la première chose importante. Si vous souhaitez atteindre le niveau de cadre et fournir le cadre avec de nombreux caches de données partagées, il doit y avoir de nombreux problèmes de réquisition de données au milieu. Bien sûr, Java fournit de nombreuses classes de packages simultanés, et vous pouvez l'utiliser, mais comment cela est fait en interne, vous devez comprendre les détails pour mieux l'utiliser, sinon il vaut mieux ne pas l'utiliser. Cet article peut ne pas expliquer ces contenus comme l'objectif, car comme le titre de titre: nous voulons parler du processeur, haha.
La même chose est dit, il semble que Java n'a rien à voir avec le processeur, alors parlons de ce qui se passe maintenant;
1. Lorsque vous rencontrez un élément partagé, notre première idée est d'assurer des opérations de lecture cohérentes par le biais de la visibilité absolue. La soi-disant visibilité signifie que chaque fois que vous souhaitez utiliser ces données, le CPU n'utilisera aucun contenu de cache et saisira les données de la mémoire. Ce processus est toujours valable pour plusieurs CPU, ce qui signifie que le CPU et la mémoire sont synchronisés pour le moment. Le CPU émettra une instruction d'assemblage similaire à Lock Addl 0 comme un bus, +0 mais ne fera rien par rapport à rien. Cependant, une fois l'instruction terminée, les opérations ultérieures n'affecteront plus l'accès d'autres threads de cet élément, qui est la visibilité absolue qu'il peut réaliser, mais ne peut pas mettre en œuvre des opérations cohérentes. C'est-à-dire que ce que volatile ne peut pas réaliser, c'est la cohérence des opérations telles que I ++ (concurrence sous plusieurs threads), car les opérations I ++ sont décomposées en:
int tmp = i; tmp = tmp + 1; i = tmp;
Ces trois étapes sont terminées. À partir de ce moment, vous pouvez également voir pourquoi I ++ peut d'abord faire d'autres choses, puis ajouter 1 à lui-même, car il est attribué la valeur à une autre variable.
2. Si nous voulons utiliser la cohérence de la concurrence multi-thread, nous devons utiliser le mécanisme de verrouillage. À l'heure actuelle, des choses comme Atomic * peuvent essentiellement répondre à ces exigences. De nombreuses méthodes de classe dangereuses sont fournies en interne. En comparant constamment les données de visibilité absolues, nous pouvons nous assurer que les données acquises sont à jour; Ensuite, nous continuerons à parler d'autres questions de processeur.
3. Dans le passé, nous n'avons pas pu exécuter le CPU pour le remplir, mais nous n'étions pas satisfaits, peu importe comment nous avons commencé à ignorer le retard entre la mémoire et le processeur. Puisque nous l'avons mentionné aujourd'hui, nous parlerons brièvement du retard. D'une manière générale, le CPU actuel a un cache à trois niveaux, et les retards sont différents à différents âges, donc le nombre spécifique ne peut être qu'à peu près juste. Les CPU d'aujourd'hui ont généralement un retard de 1 à 2N, le cache de deuxième niveau est généralement de quelques NS à environ dix ns, et le cache de troisième niveau se situe entre 30ns et 50ns, et l'accès à la mémoire atteindra généralement 70ns ou même plus (l'ordinateur se développe très rapidement, et cette valeur est uniquement pour les données sur certains CPU, pour une référence de plage); Bien que ce retard soit très petit, il est tout au niveau de la nanoseconde, vous constaterez que lorsque votre programme est divisé en opérations d'instruction, il y aura beaucoup d'interactions CPU. Si le retard de chaque interaction est si grand, les performances du système changent pour le moment;
4. Retournez au volatile mentionné tout à l'heure. Chaque fois qu'il obtient des données de mémoire, il abandonne le cache. Bien sûr, s'il devient plus lent dans certaines opérations à fil unique, elle deviendra plus lente. Parfois, nous devons le faire. Même les opérations de lecture et d'écriture nécessitent une cohérence, et même l'ensemble du bloc de données est synchronisé. Nous ne pouvons que réduire la granularité de la serrure dans une certaine mesure, mais nous ne pouvons pas avoir de serrures du tout. Même le niveau du processeur lui-même aura des restrictions au niveau d'instructions.
5. Les opérations atomiques au niveau du processeur sont généralement appelées barrières, avec des barrières de lecture, des barrières d'écriture, etc. Ils sont généralement déclenchés par un point. Lorsque plusieurs instructions du programme sont envoyées au CPU, certaines instructions ne peuvent pas être exécutées dans l'ordre du programme, et certaines doivent être exécutées dans l'ordre du programme, tant qu'elles peuvent être garanties pour être cohérentes dans l'ordre final du programme. En termes de tri, JIT changera pendant l'exécution et le niveau d'instructions du CPU changera également. La raison principale est d'optimiser les instructions d'exécution pour que le programme s'exécute plus rapidement.
6. Le niveau du CPU fonctionnera en ligne de cache sur la mémoire. La soi-disant ligne de cache lira un morceau de mémoire en continu, qui est généralement lié au modèle et à l'architecture du CPU. De nos jours, de nombreux CPU liront généralement la mémoire continue à chaque fois, et les premiers auront 32 ans, donc ce sera plus rapide lors de la traversée de certains tableaux (il est très lent en fonction de la traversée de colonne), mais ce n'est pas complètement correct. Ce qui suit comparera certaines situations opposées.
7. Si le CPU modifie les données, nous devons parler de l'état du CPU modifiant les données. Si toutes les données sont lues, elles peuvent être lues en parallèle par plusieurs threads sous plusieurs CPU. Lors de la rédaction d'opérations sur les blocs de données, il est différent. Les blocs de données seront exclusifs, modifiés, invalidation et autres états, et les données échoueront naturellement après modification. Lorsque plusieurs threads modifient le même bloc de données sous plusieurs CPU, la copie de données de bus (QPI) entre les CPU se produira. Bien sûr, si nous les modifions aux mêmes données, nous n'avons pas le choix, mais lorsque nous revenons à la ligne de cache au point 6, le problème est plus gênant. Si les données se trouvent sur le même tableau et que les éléments du tableau seront mis en cache en CPU en même temps, l'IMPI de multi-threads sera très fréquent. Parfois, ce problème se produit même si les objets assemblés sur le tableau sont assemblés, comme:
class inputInteger {private int value; public inputInteger (int i) {this.value = i;}} inputInteger [] entiers = new inputInteger [size]; for (int i = 0; i <size; i ++) {entiers [i] = new inputInteger (i);}; Pour le moment, vous pouvez voir que tout dans des entiers est des objets, et il n'y a que des références aux objets sur le tableau, mais la disposition des objets est théoriquement indépendante et ne sera pas stockée en continu. Cependant, lorsque Java alloue la mémoire d'objet, il est souvent alloué en continu dans la zone Eden. Lorsque dans la boucle FOR, si aucun autre thread n'est accessible, ces objets seront stockés ensemble. Même s'ils sont GC dans l'ancienne zone, il est très susceptible d'être mis en place. Par conséquent, la façon de modifier l'ensemble du tableau en s'appuyant sur des objets simples pour résoudre la ligne de cache semble peu fiable, car il est 4 octets. Si en mode 64, cette taille est de 24 octets (4 bytes sont remplis) et la compression du pointeur est de 16 octets; c'est-à-dire que le CPU peut correspondre à 3-4 objets à chaque fois. Comment faire le cache CPU, mais cela n'affecte pas le QPI du système. Ne pensez pas à le compléter en séparant les objets, car le processus de copie de la mémoire du processus GC est susceptible d'être copié ensemble. La meilleure façon est de le remplir. Bien qu'il s'agisse d'un peu de déchets de mémoire, c'est la méthode la plus fiable, qui est de remplir l'objet à 64 octets. Si la compression du pointeur n'est pas activée, il y a 24 bytes et il y a 40 octets pour le moment. Vous n'avez qu'à ajouter 5 longs à l'intérieur de l'objet.
Classe InputInteger {public int Value; Private Long A1, A2, A3, A4, A5;} Haha, cette méthode est très rustique, mais elle fonctionne très bien. Parfois, lorsque JVM est compilé, il constate que ces paramètres n'ont pas été effectués, il est donc tué directement pour vous. L'optimisation n'est pas valide. La méthode plus la méthode consiste à faire fonctionner simplement ces 5 paramètres dans un corps de méthode (les a tous utilisés), mais cette méthode ne l'appellera jamais.
8. Au niveau du processeur, il peut parfois ne pas être possible de faire la première chose à faire. C'est le roi. Dans le fonctionnement d'AtomicIntegerFieldUpDater, si vous appelez GetanteSet (true) dans un seul fil, vous constaterez qu'il fonctionne assez rapidement et qu'il commence à ralentir sous un processeur multi-core. Pourquoi est-il dit clairement ci-dessus? Parce que GetandSet est modifié et comparé, puis le modifie d'abord, le QPI sera très élevé, donc pour le moment, il est préférable d'obtenir d'abord les opérations, puis de le modifier; Et c'est aussi un bon moyen de l'obtenir une fois. S'il ne peut pas être obtenu, cédez et laissez d'autres fils faire d'autres choses;
9. Parfois, afin de résoudre le problème de certains processeurs occupés et non occupés, il y aura de nombreux algorithmes à résoudre. Par exemple, NUMA est l'une des solutions. Cependant, quelle que soit l'architecture plus utile dans certains scénarios, il peut ne pas être efficace pour tous les scénarios. Il existe un mécanisme de verrouillage de file d'attente pour compléter la gestion de l'état du CPU, mais cela a également le problème de la ligne de cache, car l'état change fréquemment, et les noyaux de diverses applications produiront également des algorithmes à faire pour coopérer avec le CPU, afin que le CPU puisse être utilisé plus efficacement, comme les files d'attente CLH.
Il existe de nombreux détails à ce sujet, tels que la superposition de boucles variables ordinaires, de type volatile et de séries atomiques *, qui sont complètement différentes; Boucles de réseau multidimensionnelles, boucle dans l'ordre arrière dans différentes latitudes, et il y a de nombreux détails, et je comprends pourquoi il y a une inspiration dans le processus d'optimisation réel; Les détails des serrures sont trop minces et étourdis, et au niveau inférieur du système, il y a toujours des opérations atomiques légères. Peu importe qui dit que son code ne nécessite pas de verrouillage, le meilleur peut être aussi simple que le CPU ne peut exécuter qu'une seule instruction à chaque instant. Les processeurs multicœurs auront également une zone partagée pour contrôler un certain contenu au niveau du bus, y compris le niveau de lecture, le niveau d'écriture, le niveau de mémoire, etc. Dans différents scénarios, la granularité du verrou est réduite autant que possible. Les performances du système sont évidentes et c'est un résultat normal.