L'utilisation de fichiers pour la mémoire partagée IPC, la cartographie de la mémoire est-elle une exigence ?

voix
19

Il existe quelques projets qui utilisent les MappedByteBuffers renvoyés par le FileChannel.map() de Java comme moyen d'avoir une IPC à mémoire partagée entre les JVM sur le même hôte (voir Chronicle Queue, Aeron IPC, etc.). Pour autant que je sache, cet api se trouve juste au-dessus de l'appel mmap. Cependant, l'implémentation de Java ne permet pas les mappages anonymes (non sauvegardés par des fichiers).

Ma question est la suivante : sur Java (1.8) et Linux (3.10), les MappedByteBuffers sont-ils vraiment nécessaires pour mettre en œuvre la mémoire partagée IPC, ou un accès à un fichier commun fournirait-il la même fonctionnalité ? (Cette question ne concerne pas l'implication en termes de performance de l'utilisation ou non d'un MappedByteBuffer)

Voici ce que je comprends :

  1. Lorsque Linux charge un fichier à partir d'un disque, il copie le contenu de ce fichier sur des pages en mémoire. Cette région de la mémoire est appelée le cache des pages. Pour autant que je sache, il le fait indépendamment de la méthode Java (FileInputStream.read(), RandomAccessFile.read(), FileChannel.read(), FileChannel.map()) ou de la méthode native utilisée pour lire le fichier (désignée par free et surveillant la valeur du cache).
  2. Si un autre processus tente de charger le même fichier (alors qu'il réside encore dans le cache), le noyau le détecte et n'a pas besoin de recharger le fichier. Si le cache des pages est plein, les pages seront expulsées - les pages sales étant réécrites sur le disque. (Les pages sont également réécrites s'il y a un flush explicite sur le disque, et périodiquement, avec un thread du noyau).
  3. Avoir un (gros) fichier déjà dans le cache est un gain de performance significatif, bien plus que les différences basées sur les méthodes Java que nous utilisons pour ouvrir/lire ce fichier.
  4. Un programme C appelant le système mmap peut faire un mapping ANONYME, qui alloue essentiellement des pages dans le cache qui ne sont pas soutenues par un fichier réel (donc il n'y a pas besoin d'émettre des écritures réelles sur le disque), mais Java ne semble pas offrir cela (est-ce que le mapping d'un fichier dans tmpfs accomplirait la même chose ?)
  5. Si un fichier est chargé en utilisant l'appel système mmap (C) ou via FileChannel.map() (Java), ce sont essentiellement les pages du fichier (dans le cache) qui sont chargées directement dans l'espace d'adressage du processus. En utilisant d'autres méthodes pour ouvrir un fichier, le fichier est chargé dans des pages qui ne se trouvent pas dans l'espace d'adressage du processus, et ensuite les différentes méthodes de lecture/écriture de ce fichier copient quelques octets depuis/vers ces pages dans un tampon dans l'espace d'adressage du processus. Il y a un avantage évident à éviter cette copie, mais ma question ne porte pas sur les performances.

En résumé, si je comprends bien - bien que la cartographie offre un avantage en termes de performances, il semble qu'elle n'offre aucune fonctionnalité de mémoire partagée que nous n'obtenons pas déjà grâce à la nature même de Linux et du cache des pages.

Alors, s'il vous plaît, faites-moi savoir où se situe mon incompréhension.

Merci.

Créé 22/05/2020 à 21:20
source utilisateur
Dans d'autres langues...                            


2 réponses

voix
0

Trois points méritent d'être mentionnés : les performances, les changements simultanés et l'utilisation de la mémoire.

Vous avez raison d'estimer que le système basé sur le MMAP offre généralement un avantage en termes de performances par rapport à l'OI basé sur les fichiers. En particulier, l'avantage de performance est significatif si le code exécute beaucoup de petites E/S à un point arbitraire du fichier.

pensez à changer le N-ième octet : avec mmap buffer[N] = buffer[N] + 1, et avec l'accès basé sur les fichiers, vous avez besoin (au moins) de 4 appels système de vérification d'erreurs :

   seek() + error check
   read() + error check
   update value
   seek() + error check
   write + error check

Il est vrai que le nombre d'E/S réelles (sur le disque) est très probablement le même.

Le deuxième point qui mérite d'être noté est l'accès simultané. Avec les OI basées sur les fichiers, vous devez vous inquiéter d'un éventuel accès concurrent. Vous devrez émettre un verrouillage explicite (avant la lecture), et un déverrouillage (après l'écriture), pour empêcher deux processus d'accéder incorrectement à la valeur en même temps. Avec la mémoire partagée, les opérations atomiques peuvent éliminer la nécessité d'un verrouillage supplémentaire.

Le troisième point est l'utilisation réelle de la mémoire. Dans les cas où la taille des objets partagés est importante, l'utilisation de la mémoire partagée peut permettre à un grand nombre de processus d'accéder aux données sans avoir à allouer de mémoire supplémentaire. Si les systèmes sont limités par la mémoire, ou s'ils doivent fournir des performances en temps réel, cela pourrait être la seule façon d'accéder aux données.

Créé 29/05/2020 à 10:35
source utilisateur

voix
0

Ma question est la suivante : sur Java (1.8) et Linux (3.10), les MappedByteBuffers sont-ils vraiment nécessaires pour mettre en œuvre la mémoire partagée IPC, ou un accès à un fichier commun permettrait-il d'obtenir la même fonctionnalité ?

Cela dépend de la raison pour laquelle vous souhaitez mettre en œuvre la mémoire partagée IPC.

Vous pouvez clairement mettre en œuvre la CIB sans mémoire partagée, par exemple sur des sockets. Ainsi, si vous ne le faites pas pour des raisons de performance, il n'est pas du tout nécessaire de faire de la mémoire partagée !

La performance doit donc être à la base de toute discussion.

L'accès aux fichiers via les API Java classic io ou nio ne fournit pas de fonctionnalité ou de performance de mémoire partagée.

La principale différence entre les E/S de fichiers ou les E/S de sockets ordinaires et la mémoire partagée IPC est que la première exige que les applications fassent readexplicitement et les writeappels système envoient et reçoivent des messages. Cela implique des appels système supplémentaires, et implique que le noyau copie les données. De plus, s'il y a plusieurs threads, il faut soit un "canal" séparé entre chaque paire de threads, soit quelque chose pour multiplexer plusieurs "conversations" sur un canal partagé. Dans ce dernier cas, le canal partagé peut devenir un goulot d'étranglement en matière de concurrence.

Notez que ces surcharges sont orthogonales au cache des pages Linux.

En revanche, dans le cas de la CIB mise en œuvre en utilisant la mémoire partagée, il n'y a pas readd'writeappels système, ni d'étape de copie supplémentaire. Chaque "canal" peut simplement utiliser une zone distincte de la mémoire tampon cartographiée. Un thread d'un processus écrit des données dans la mémoire partagée et celles-ci sont presque immédiatement visibles pour le second processus.

La mise en garde est que les processus doivent 1) se synchroniser et 2) mettre en place des barrières de mémoire pour s'assurer que le lecteur ne voit pas de données périmées. Mais ces deux éléments peuvent être mis en œuvre sans appel système.

Lors du nettoyage, l'IPC en mémoire partagée utilisant des fichiers de mémoire mappée >>est plus rapide que l'utilisation de fichiers ou de sockets conventionnels, et c'est pourquoi les gens le font.


Vous avez également demandé implicitement si la CIB à mémoire partagée peut être mise en œuvre sans fichiers mappés en mémoire.

  • Un moyen pratique serait de créer un fichier mappé en mémoire pour un fichier qui vit dans un système de fichiers en mémoire uniquement ; par exemple un "tmpfs" sous Linux.

    Techniquement, il s'agit toujours d'un fichier en mémoire mappée. Cependant, vous n'encourez pas de frais supplémentaires de transfert de données sur le disque, et vous évitez les problèmes de sécurité potentiels liés au fait que des données IPC privées se retrouvent sur le disque.

  • Vous pourriez en théorie mettre en place un segment partagé entre deux processus en procédant comme suit :

    • Dans le processus parent, utilisez mmap pour créer un segment avec MAP_ANONYMOUS | MAP_SHARED.
    • Processus de l'enfant de la fourchette. Ceux-ci finiront tous par partager le segment entre eux et avec le processus parent.

    Cependant, l'implémentation de ce processus pour un processus Java serait ... difficile. AFAIK, Java n'est pas compatible avec cela.

Référence :

Créé 31/05/2020 à 06:17
source utilisateur

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more