Czy używanie plików do IPC z pamięcią dzieloną jest wymagane?

głosy
19

Istnieje kilka projektów, które używają MappedByteBuffers zwracanych przez Java FileChannel.map() jako sposób na współdzielenie pamięci IPC pomiędzy JVM na tym samym hoście (zobacz Chronicle Queue, Aeron IPC, itd.). Z tego co mogę powiedzieć, to api po prostu siedzi na szczycie połączenia mmap. Jednakże implementacja Javy nie pozwala na anonimowe (nie poparte plikami) mapowanie.

Moje pytanie brzmi, czy w przypadku Javy (1.8) i Linuksa (3.10), MappedByteBuffers są naprawdę niezbędne do implementacji IPC z pamięcią dzieloną, czy też dostęp do wspólnego pliku zapewniłby taką samą funkcjonalność? (To pytanie nie dotyczy wpływu wykorzystania MappedByteBuffera na wydajność, czy też nie)

Oto moje rozumienie:

  1. Kiedy Linux ładuje plik z dysku, kopiuje zawartość tego pliku do stron w pamięci. Ten obszar pamięci nazywany jest page cache. O ile mogę powiedzieć, robi to niezależnie od tego, która z metod Java (FileInputStream.read(), RandomAccessFile.read(), FileChannel.read(), FileChannel.map()) czy też natywnej metody jest używana do odczytu pliku (obseved with free and monitoring the cache value).
  2. Jeśli inny proces próbuje załadować ten sam plik (podczas gdy wciąż znajduje się on w cache), jądro wykrywa to i nie musi go ładować ponownie. Jeśli bufor strony zostanie zapełniony, strony zostaną wyeksmitowane - brudne zostaną zapisane z powrotem na dysk. (Strony są również zapisywane z powrotem, jeśli istnieje wyraźne spłukiwanie na dysk, a okresowo, za pomocą wątku jądra).
  3. Posiadanie (dużego) pliku już w pamięci podręcznej jest znaczącym wzrostem wydajności, o wiele większym niż różnice na podstawie których używamy metod Java do otwarcia/odczytu tego pliku.
  4. Program C wywołujący wywołanie systemu mmap może wykonać ANONYMOUS mapping, który zasadniczo przydziela strony w pamięci podręcznej, które nie są obsługiwane przez rzeczywisty plik (nie ma więc potrzeby wydawania rzeczywistych zapisów na dysku), ale Java nie wydaje się tego oferować (czy mapowanie pliku w tmpfs osiągnęłoby to samo?)
  5. Jeśli plik jest ładowany przy użyciu wywołania systemowego mmap (C) lub poprzez FileChannel.map() (Java), zasadniczo strony pliku (w cache) są ładowane bezpośrednio do przestrzeni adresowej procesu. Używaj±c innych metod do otwarcia pliku, plik jest ładowany do stron, które nie znajduj± się w przestrzeni adresowej procesu, a następnie różne metody do odczytu/zapisu tego pliku kopi± niektóre bajty z/do tych stron do bufora w przestrzeni adresowej procesu. Jest oczywistą korzyścią unikania tej kopii, ale moje pytanie nie jest związane z wydajnością.

Podsumowując, jeśli dobrze rozumiem - podczas gdy mapowanie oferuje przewagę wydajnościową, nie wydaje się, aby oferowało jakąkolwiek funkcjonalność pamięci współdzielonej, której nie otrzymujemy już tylko z natury Linuksa i page cache'a.

Więc, proszę, daj mi znać, gdzie moje rozumienie jest wyłączone.

Dzięki.

Utwórz 22/05/2020 o 21:20
źródło użytkownik
W innych językach...                            


2 odpowiedzi

głosy
0

Warto wspomnieć o trzech punktach: wydajność, i zmiany równoczesne, i wykorzystanie pamięci.

Masz rację w ocenie, że MMAP zazwyczaj oferuje przewagę wydajności nad IO opartym na plikach. W szczególności, przewaga wydajności jest znacząca, jeśli kod wykonuje dużo małych IO w artbitrycznym punkcie pliku.

rozważ zmianę N-tego bajtu: za pomocą mmapy buffer[N] = buffer[N] + 1, a przy dostępie opartym na plikach musisz (co najmniej) 4 razy sprawdzić błędy wywołania systemowego:

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

To prawda, że liczba rzeczywistych IO (do dysku) najprawdopodobniej jest taka sama.

Druga kwestia warta odnotowania to równoczesny dostęp. W przypadku IO opartych na plikach, trzeba się martwić o potencjalny jednoczesny dostęp. Będziesz musiał wydać wyraźną blokadę (przed odczytem), i odblokować (po zapisie), aby zapobiec dwóm procesom nieprawidłowego dostępu do wartości w tym samym czasie. Dzięki pamięci współdzielonej, operacje atomowe mogą wyeliminować potrzebę dodatkowego blokowania.

Trzecim punktem jest rzeczywiste wykorzystanie pamięci. W przypadkach, gdy wielkość obiektów współdzielonych jest znaczna, użycie pamięci współdzielonej może umożliwić dużej liczbie procesów dostęp do danych bez konieczności przydzielania dodatkowej pamięci. W przypadku systemów ograniczonych pamięcią lub systemów, które muszą zapewniać wydajność w czasie rzeczywistym, może to być jedyny sposób uzyskania dostępu do danych.

Odpowiedział 29/05/2020 o 10:35
źródło użytkownik

głosy
0

Moje pytanie brzmi, czy w przypadku Javy (1.8) i Linuksa (3.10) MappedByteBuffers są naprawdę niezbędne do wdrożenia IPC z pamięcią dzieloną, czy też dostęp do wspólnego pliku zapewniłby taką samą funkcjonalność?

Zależy to od tego, dlaczego chcesz zaimplementować IPC z pamięcią dzieloną.

Można wyraźnie zaimplementować IPC bez wspólnej pamięci, np. nad gniazdami. Tak więc, jeśli nie robisz tego ze względów wydajnościowych, nie jest konieczne wykonywanie IPC z pamięcią dzieloną w ogóle!

Tak więc wydajność musi być u podstaw każdej dyskusji.

Dostęp do plików poprzez klasyczne API Java io lub nio nie zapewnia funkcjonalności ani wydajności pamięci współdzielonej.

Główną różnicą pomiędzy zwykłymi I/O plików lub Socket I/O a IPC z pamięcią dzieloną jest to, że te pierwsze wymagają od aplikacji wyraźnego wykonywania readi writesyscall do wysyłania i odbierania wiadomości. Wiąże się to z dodatkowymi syscallami, a także z kopiowaniem danych przez jądro. Ponadto, jeśli istnieje wiele wątków, potrzebny jest osobny "kanał" pomiędzy każdą parą wątków lub coś w celu multipleksowania wielu "rozmów" na wspólnym kanale. To ostatnie może prowadzić do tego, że kanał współdzielony stanie się wąskim gardłem współbieżności.

Zauważ, że te koszty ogólne są ortogonalne w stosunku do pamięci podręcznej strony Linuxa.

Z kolei w IPC zaimplementowanym z wykorzystaniem pamięci współdzielonej nie ma readi writesyscali, i nie ma dodatkowego kroku kopiowania. Każdy "kanał" może po prostu korzystać z oddzielnego obszaru mapowanego bufora. Wątek w jednym procesie zapisuje dane do pamięci współdzielonej i jest prawie natychmiast widoczny dla drugiego procesu.

Zastrzeżenie jest takie, że procesy muszą 1) zsynchronizować, oraz 2) wdrożyć bariery pamięci, aby upewnić się, że czytnik nie widzi zużytych danych. Ale obie te bariery mogą być zaimplementowane bez syscentryzowania.

W zmywarce, IPC z pamięcią dzieloną, używając plików z mapą pamięci >>jest<< szybszy niż używając konwencjonalnych plików lub gniazdek, i dlatego ludzie to robią.


Pytasz również domyślnie, czy IPC z pamięcią dzieloną może być zaimplementowane bez plików mapowanych w pamięci.

  • Praktycznym sposobem byłoby stworzenie pliku z mapą pamięci dla pliku, który żyje w systemie plików tylko z pamięcią; np. "tmpfs" w Linuksie.

    Technicznie rzecz biorąc, jest to nadal plik z mapą pamięci. Jednakże, nie ponosisz ogólnych kosztów przepłukiwania danych na dysk i unikasz potencjalnych obaw związanych z bezpieczeństwem prywatnych danych IPC, które trafiają na dysk.

  • Teoretycznie można by wdrożyć wspólny segment pomiędzy dwoma procesami, wykonując następujące czynności:

    • W procesie nadrzędnym, użyj mmapy, aby utworzyć segment z MAP_ANONYMOUS | MAP_SHARED.
    • Widelec procesów dziecięcych. W ten sposób wszystkie segmenty będą współdzielone między sobą i procesami rodzicielskimi.

    Jednakże implementacja tego dla procesu Java byłaby ... wyzwaniem. AFAIK, Java tego nie obsługuje.

Odniesienie:

Odpowiedział 31/05/2020 o 06:17
źródło użytkownik

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