2010-06-16 3 views
17

Il y a quelque chose qui me dérange avec le modèle de mémoire Java (si je comprends même tout correctement). S'il y a deux threads A et B, il n'y a aucune garantie que B verra jamais une valeur écrite par A, à moins que A et B ne se synchronisent sur le même moniteur.Le modèle de mémoire Java (JSR-133) implique-t-il que la saisie d'un moniteur entraîne le vidage du (des) cache (s) de données du processeur?

Pour toute architecture système garantissant la cohérence du cache entre les threads, il n'y a pas de problème. Mais si l'architecture ne prend pas en charge la cohérence du cache dans le matériel, cela signifie essentiellement que chaque fois qu'un thread entre dans un moniteur, toutes les modifications de mémoire effectuées précédemment doivent être validées dans la mémoire principale et le cache doit être invalidé. Et il doit être le cache de données complet, pas seulement quelques lignes, car le moniteur n'a aucune information quelles variables dans la mémoire il garde. Mais cela affecterait sûrement les performances de toute application devant être synchronisée fréquemment (en particulier les tâches telles que les files d'attente de travaux avec des tâches en cours d'exécution). Alors Java peut-il fonctionner raisonnablement bien sur des architectures sans cohérence de cache matériel? Si non, pourquoi le modèle de mémoire ne donne-t-il pas de meilleures garanties de visibilité? Ne serait-il pas plus efficace si le langage exigeait des informations ce qui est gardé par un moniteur? Comme je le vois le modèle de mémoire nous donne le pire des deux mondes, le besoin absolu de se synchroniser, même si la cohérence du cache est garantie dans le matériel, et d'autre part une mauvaise performance sur les architectures incohérentes. Donc, ne devrait-il pas être plus strict (exiger des informations ce qui est surveillé par un moniteur) ou plus perdre et limiter les plates-formes potentielles à des architectures cohérentes en cache?

Comme c'est le cas maintenant, cela n'a pas beaucoup de sens pour moi. Est-ce que quelqu'un peut éclaircir pourquoi ce modèle de mémoire spécifique a été choisi? EDIT: Mon utilisation de strict et de perdre était un mauvais choix en rétrospective. J'ai utilisé "strict" pour le cas où moins de garanties sont faites et "perdre" pour le contraire. Pour éviter toute confusion, il est probablement préférable de parler en termes de garanties plus fortes ou plus faibles.

+0

Une chose qui doit être commentée est l'instruction "synchroniser sur le même moniteur". Si je comprends bien, la synchronisation sur le même moniteur garantit l'ordre d'exécution autour de ce moniteur. Cependant, en termes de mémoire cache, l'acquisition de _any_ bloc synchronisé force le rechargement du cache et la libération de _any_ lock force le vidage de toutes les écritures de la mémoire du CPU. – Gray

+0

Quel arc spécifique aviez-vous en tête? – curiousguy

Répondre

6

la nécessité absolue de synchroniser, même si la cohérence du cache est garantie dans matériel

Oui, mais il suffit de raisonner sur le modèle mémoire de Java, pas contre une architecture matérielle particulier votre programme se passe sur. De plus, il ne s'agit pas seulement du matériel, le compilateur et JIT eux-mêmes peuvent réorganiser les instructions causant un problème de visibilité. Les constructions de synchronisation dans Java adressent la visibilité & atomicité de manière cohérente à tous les niveaux possibles de transformation de code (par exemple compilateur/JIT/CPU/cache).

et d'autre part les mauvaises performances sur des architectures incohérentes (full vidages de cache)

Peut-être que mal compris s/t, mais avec des architectures incohérentes, vous devez synchroniser des sections critiques de toute façon. Sinon, vous rencontrerez toutes sortes de conditions de course en raison de la réorganisation. Je ne vois pas pourquoi le Java Memory Model rend la situation pire.

devrait-il pas plus stricte (exiger informations ce qui est gardée par un moniteur)

Je ne pense pas qu'il soit possible de dire à la CPU de vider une partie particulière du cache du tout. Le mieux que le compilateur peut faire est d'émettre des clôtures de mémoire et de laisser le processeur décider quelles parties du cache ont besoin d'être vidées - c'est encore plus grossier que ce que vous cherchez, je suppose. Même si un contrôle plus fin est possible, je pense que cela rendrait la programmation simultanée encore plus difficile (c'est déjà assez difficile). Autant dire que le Java 5 MM (tout comme le .NET CLR MM) est plus "strict" que les modèles mémoire d'architectures communes comme x86 et IA64. Par conséquent, il rend le raisonnement à ce sujet relativement plus simple. Pourtant, il ne devrait évidemment pas offrir un rapport s/t plus proche de la consistance séquentielle, car cela nuirait considérablement aux performances car moins d'optimisations de compilateur/JIT/CPU/cache pourraient être appliquées.

+0

Je n'ai pas réfléchi aux problèmes de réorganisation, mais si une limite de réordonnancement est nécessaire, il suffit d'accéder à un champ volatile pour en obtenir un sans avoir besoin de synchronisation. Si le modèle de mémoire comprenait des garanties sur la visibilité des champs non volatils, un seul champ volatil pourrait être utilisé pour communiquer la validité de n'importe quel nombre de champs non volatils entre deux threads (bien que l'on pollue ce champ volatile). – Durandal

+0

En ce qui concerne les architectures incohérentes, pour un champ volatile, le JIT peut utiliser des instructions qui contournent le cache OU, si elles sont absentes, vider la ligne de cache unique qui couvre le champ. Avec la sémantique de synchronized, il n'y a pas de restriction quant au (x) champ (s) à publier sur un autre thread, donc j'ai conclu que le cache entier doit être vidé dans ce cas. C'est ce qui me dérange. Vider le cache entier (potentiellement mégaoctets de cache sale), même si seulement quelques champs doivent être publiés sur un autre thread semble inefficace (bien sûr, cela dépend fortement de l'application) – Durandal

+0

Je dois admettre que je n'ai qu'une connaissance intime d'un famille de processeurs et celle-ci inclut des instructions pour vider/invalider des lignes simples, ainsi qu'une page de mémoire (comme dans la page MMU).Comme il s'agit d'un aspect lié aux performances pour les pilotes de périphérique qui exécutent DMA, je suppose que toute architecture raisonnable a un moyen d'y parvenir. Mais je pourrais être complètement faux avec cette hypothèse. – Durandal

1

Les caches auxquels JVM a accès ne sont en réalité que des registres CPU. Puisqu'il n'y en a pas beaucoup, les jeter à la sortie du moniteur n'est pas une grosse affaire.

EDIT: (en général) les caches de mémoire ne sont pas sous le contrôle de la machine virtuelle Java, JVM ne peut pas choisir de lecture/écriture/flush ces caches, donc les oublier dans cette discussion

imaginer chaque CPU a 1 000 000 registres. JVM les exploite volontiers pour faire des calculs rapides et fous - jusqu'à ce qu'il tombe sur l'entrée/la sortie du moniteur, et qu'il vole 1 000 000 de registres dans la couche de cache suivante. Si nous vivons dans ce monde, soit Java doit être suffisamment intelligent pour analyser quels objets ne sont pas partagés (la majorité des objets ne le sont pas), ou il doit demander aux programmeurs de le faire. Le modèle de mémoire java est un modèle de programmation simplifié qui permet aux programmeurs moyens de créer des algorithmes de multithreading OK. par «simplifié», je veux dire qu'il pourrait y avoir 12 personnes dans le monde entier qui lisent vraiment le chapitre 17 de JLS et le comprennent réellement.

+2

Je ne pense pas que ce soit correct. L'unité centrale dispose également de caches mémoire de l'ordre de mégaoctets devant la mémoire principale http://en.wikipedia.org/wiki/CPU_cache. Le problème est lorsque ce cache vide ses pages sales dans la mémoire principale et invalide les pages qui ont été mises à jour dans la mémoire principale. – Gray

+0

La sémantique du cache et du registre est différente - les registres sont des threads locaux, tandis que le cache peut être partagé entre threads (ou même éventuellement des processus, selon l'architecture). – Durandal

+0

mecs, les caches mémoire dont vous avez parlé ne sont pas programmables par JVM, donc ils ne sont pas pertinents pour notre discussion. – irreputable

4

La réponse serait que la plupart des multiprocesseurs sont en cache-cohérent, y compris les grands systèmes NUMA, qui presque? sont toujours ccNUMA.

Je pense que vous êtes un peu confus quant à la façon dont la cohérence du cache est réalisée dans la pratique. Tout d'abord, les caches peuvent être cohérentes/incohérentes par rapport à plusieurs autres choses sur le système:

  • Devices
  • (mémoire modifiés par) DMA
  • caches de données vs caches d'instructions
  • Caches sur d'autres noyaux/processeurs (une question est sur le point)
  • ...

Quelque chose doit être fait pour maintenir la cohérence. Lorsque vous travaillez avec des périphériques et DMA, sur des architectures avec des caches incohérents par rapport aux DMA/périphériques, vous devez soit ignorer le cache (et éventuellement le tampon d'écriture), soit invalider/vider le cache autour des opérations impliquant DMA/devices. De même, lors de la génération dynamique de code, vous devrez peut-être vider le cache d'instructions.Quand il s'agit de caches CPU, la cohérence est obtenue en utilisant un protocole de cohérence, tel que MESI, MOESI, ... Ces protocoles définissent les messages à envoyer entre les caches en réponse à certains événements (par exemple: invalider-demandes à d'autres caches lorsqu'une cacheline non-exclusive est modifiée, ...).

Bien que cela soit suffisant pour maintenir la cohérence (éventuelle), il ne garantit pas l'ordre ou les modifications sont immédiatement visibles par les autres processeurs. Ensuite, il y a aussi des tampons d'écriture qui retardent les écritures. Ainsi, chaque architecture de CPU fournit des garanties de commande (par exemple, des accès avant qu'un magasin aligné ne puisse être réorganisé après le magasin) et/ou fournisse des instructions (barrières de mémoire/clôtures) pour demander de telles garanties. En fin de compte, entrer/sortir d'un moniteur n'entraîne pas le vidage de la mémoire cache, mais peut entraîner la vidange du tampon d'écriture et/ou le décrochage en attente de la fin des lectures.

+0

Je suis conscient qu'avec la dominance de x86 le problème est de nature plus théorique, mais si on suppose qu'il y a un support de cohérence dans le hardware de toute façon le modèle de mémoire Java semble être trop conservateur dans sa définition. L'exemple avec les périphériques DMA ne fait pas vraiment la lumière sur le sujet, puisque le pilote d'un tel périphérique sera responsable de vider les caches lorsque nécessaire - et contrairement à Java, le pilote * sait exactement quelle zone de mémoire va être affectée. – Durandal

+0

Le point sur les dispositifs et DMA était d'expliquer qu'il y a beaucoup de significations pour "incohérent", et cela peut induire les gens en erreur à croire qu'une architecture aux caches cohérentes est incohérente. – ninjalj

+0

Et à propos de la cohérence du cache, comme je l'explique, les caches ne sont pas immédiatement cohérents, mais un protocole assure une cohérence éventuelle. Les clôtures empêchent les problèmes d'ordre et de visibilité dus aux tampons d'écriture, aux files d'attente d'invalidation, aux caches en banque, ... – ninjalj

5

Les architectures existantes garantissent la cohérence du cache, mais elles ne garantissent pas la cohérence séquentielle - les deux choses sont différentes. Depuis seq. la cohérence n'est pas garantie, certains réordonnances sont autorisées par le matériel et vous avez besoin de sections critiques pour les limiter. Les sections critiques s'assurent que ce qu'un thread écrit devient visible à un autre (c.-à-d., Ils empêchent les courses de données), et ils empêchent également les conditions de course classiques (si deux threads incrémentent la même variable, vous en avez besoin pour chaque thread le lecture de la valeur courante et l'écriture de la nouvelle valeur sont indivisibles).

De plus, le modèle d'exécution n'est pas aussi cher que vous le décrivez. Sur la plupart des architectures existantes, cohérentes dans le cache mais non séquentielles, lorsque vous relâchez un verrou, vous devez vider les écritures en attente, et lorsque vous en aurez un, vous devrez peut-être faire quelque chose pour que les lectures futures ne lisent pas les valeurs périmées. la plupart du temps, cela signifie simplement empêcher que les lectures soient déplacées trop tôt, car le cache reste cohérent; mais les lectures ne doivent toujours pas être déplacées. Enfin, vous semblez penser que le modèle de mémoire de Java (JMM) est particulier, alors que les fondations sont aujourd'hui assez modernes et similaires à Ada, les verrous POSIX (selon l'interprétation de la norme) et le modèle de mémoire C/C++. Vous pouvez lire le livre de recettes JSR-133 qui explique comment le JMM est implémenté sur les architectures existantes: http://g.oswego.edu/dl/jmm/cookbook.html.

Questions connexes