najszybsza (niska latencja) metoda komunikacji między procesami między Javą a C / C ++

100

Mam aplikację Java, łączącą się przez gniazdo TCP z „serwerem” opracowanym w C / C ++.

zarówno aplikacja, jak i serwer działają na tej samej maszynie, na Solarisie (ale rozważamy ostateczną migrację do Linuksa). typ wymienianych danych to proste komunikaty (login, login ACK, potem klient o coś pyta, serwer odpowiada). każda wiadomość ma około 300 bajtów długości.

Obecnie używamy gniazd i wszystko jest w porządku, jednak szukam szybszego sposobu na wymianę danych (mniejsze opóźnienie), przy użyciu metod IPC.

Przeszukałem sieć i znalazłem odniesienia do następujących technologii:

  • pamięć współdzielona
  • Rury
  • kolejki
  • a także tak zwane DMA (bezpośredni dostęp do pamięci)

ale nie mogłem znaleźć odpowiedniej analizy ich występów, ani tego, jak zaimplementować je zarówno w JAVA, jak i C / C ++ (żeby mogli ze sobą rozmawiać), może poza rurami, które mogłem sobie wyobrazić.

Czy ktoś może wypowiedzieć się na temat wydajności i wykonalności każdej metody w tym kontekście? jakiś wskaźnik / link do przydatnych informacji o implementacji?


EDYTUJ / AKTUALIZUJ

podążając za komentarzem i odpowiedziami, które tutaj otrzymałem, znalazłem informacje o gniazdach domeny Unix, które wydają się być zbudowane tuż nad potokami i zaoszczędzą mi cały stos TCP. jest specyficzny dla platformy, więc planuję przetestować go z JNI lub z judsami lub junixsocket .

następnymi możliwymi krokami byłaby bezpośrednia implementacja potoków, a następnie pamięć współdzielona, ​​chociaż ostrzegano mnie o dodatkowym poziomie złożoności ...


dzięki za pomoc

Bastien
źródło
7
W twoim przypadku może to być przesada, ale rozważ zeromq.org
jfs
to interesujące, jednak pomysł polegałby na użyciu najpierw metod "ogólnych" (jak w systemie operacyjnym lub dostarczonym przez język), dlatego wspomniałem o kolejkach i pamięci współdzielonej.
Bastien,
2
Zobacz także stackoverflow.com/questions/904492
MSalters
Nie zapomnij zmapowanych plików lub tylko UDP.
10
UDP wolniejszy niż TCP ??? hmmm ... proszę o dowód
Boppity Bop

Odpowiedzi:

103

Właśnie przetestowałem opóźnienie z Javy na moim Corei5 2,8 GHz, tylko wysłanie / odebranie jednego bajtu, 2 procesy Java właśnie uruchomione, bez przypisywania określonych rdzeni procesora do zestawu zadań:

TCP         - 25 microseconds
Named pipes - 15 microseconds

Teraz wyraźnie określamy podstawowe maski, takie jak zestaw zadań 1 java Srv lub zestaw zadań 2 java Cli :

TCP, same cores:                      30 microseconds
TCP, explicit different cores:        22 microseconds
Named pipes, same core:               4-5 microseconds !!!!
Named pipes, taskset different cores: 7-8 microseconds !!!!

więc

TCP overhead is visible
scheduling overhead (or core caches?) is also the culprit

W tym samym czasie Thread.sleep (0) (co, jak pokazuje strace, powoduje wykonanie pojedynczego wywołania jądra Linuksa schedule_yield ()) zajmuje 0,3 mikrosekundy - więc nazwane potoki zaplanowane na pojedynczy rdzeń nadal mają dużo narzutów

Niektóre pomiary pamięci współdzielonej: 14 września 2009 r. - Solace Systems ogłosiło dzisiaj, że jego interfejs API Unified Messaging Platform może osiągnąć średnie opóźnienie poniżej 700 nanosekund przy użyciu transportu pamięci współdzielonej. http://solacesystems.com/news/fastest-ipc-messaging/

PS - wypróbowałem pamięć współdzieloną następnego dnia w postaci plików mapowanych w pamięci, jeśli oczekiwanie zajęte jest akceptowalne, możemy zmniejszyć opóźnienie do 0,3 mikrosekundy na przekazanie pojedynczego bajtu kodem takim jak ten:

MappedByteBuffer mem =
  new RandomAccessFile("/tmp/mapped.txt", "rw").getChannel()
  .map(FileChannel.MapMode.READ_WRITE, 0, 1);

while(true){
  while(mem.get(0)!=5) Thread.sleep(0); // waiting for client request
  mem.put(0, (byte)10); // sending the reply
}

Uwagi: Thread.sleep (0) jest potrzebny, aby 2 procesy mogły zobaczyć swoje zmiany (nie znam jeszcze innego sposobu). Jeśli 2 procesy są zmuszone do tego samego rdzenia z zestawem zadań, opóźnienie wynosi 1,5 mikrosekundy - to jest opóźnienie przełączania kontekstu

PPS - a 0,3 mikrosekundy to dobra liczba! Poniższy kod zajmuje dokładnie 0,1 mikrosekundy, wykonując tylko konkatenację pierwotnych ciągów:

int j=123456789;
String ret = "my-record-key-" + j  + "-in-db";

PPPS - mam nadzieję, że to nie jest zbyt odbiegające od tematu, ale w końcu próbowałem zastąpić Thread.sleep (0) inkrementacją statycznej, lotnej zmiennej int (JVM opróżnia pamięci podręczne procesora) i otrzymałem - nagraj! - 72 nanosekund opóźnienia komunikacji procesu java-to-java !

Jednak, gdy są zmuszone do pracy z tym samym rdzeniem procesora, maszyny JVM z przyrostem lotnym nigdy nie przekazują sobie kontroli, powodując w ten sposób dokładnie 10 milisekund opóźnienia - kwant czasu w Linuksie wydaje się wynosić 5 ms ... Więc powinno to być używane tylko wtedy, gdy jest zapasowy rdzeń - w przeciwnym razie sleep (0) jest bezpieczniejsze.

Andriy
źródło
dzięki Andriy, bardzo szczegółowe badanie, które mniej więcej pasuje do moich pomiarów dla TCP, więc to dobre odniesienie. Chyba zajrzę do nazwanych rur.
Bastien,
Czyli zastąpienie wątku (uśpienia) zwiększeniem zmiennego statycznego int powinno być wykonywane tylko wtedy, gdy można przypiąć proces do różnych rdzeni? Nie wiedziałem też, że możesz to zrobić? Myślałem, że system operacyjny decyduje?
mezamorficzny
3
Spróbuj LockSupport.parkNanos (1), powinno zrobić to samo.
reccles
Bardzo dobrze. Możesz jednak zrobić lepiej (jak w przypadku opóźnienia RTT 5-7us) dla pingów TCP. Zobacz tutaj: psy-lob-saw.blogspot.com/2012/12/…
Nitsan Wakart
1
Dalsze badanie wykorzystania plików mapowanych w pamięci jako pamięci współdzielonej do obsługi kolejki IPC w Javie: psy-lob-saw.blogspot.com/2013/04/lock-free-ipc-queue.html, osiągając 135 mln wiadomości na sekundę. Zobacz także moją odpowiedź poniżej dotyczącą badania porównawczego opóźnienia według metody.
Nitsan Wakart
10

DMA to metoda, za pomocą której urządzenia sprzętowe mogą uzyskać dostęp do fizycznej pamięci RAM bez przerywania pracy procesora. Np. Typowym przykładem jest kontroler dysku twardego, który może kopiować bajty bezpośrednio z dysku do pamięci RAM. W związku z tym nie ma zastosowania do IPC.

Współdzielona pamięć i potoki są obsługiwane bezpośrednio przez nowoczesne systemy operacyjne. Jako takie są dość szybkie. Kolejki to zazwyczaj abstrakcje, np. Zaimplementowane na gniazdach, rurach i / lub pamięci współdzielonej. Może to wyglądać wolniejszym mechanizmu, ale alternatywą jest to, że Ci stworzyć taką abstrakcję.

MSalters
źródło
w przypadku DMA, dlaczego w takim razie mogę przeczytać wiele rzeczy związanych z RDMA (jako Remote Direct Memory Access), które miałyby zastosowanie w całej sieci (szczególnie z InfiniBand) i zrobić to samo. Właściwie staram się osiągnąć odpowiednik BEZ sieci (ponieważ wszystko jest w tym samym pudełku).
Bastien,
RDMA to ta sama koncepcja: kopiowanie bajtów w sieci bez przerywania pracy procesorów po obu stronach. Nadal nie działa na poziomie procesu.
MSalters
10

Pytanie zostało zadane jakiś czas temu, ale możesz być zainteresowany https://github.com/peter-lawrey/Java-Chronicle, który obsługuje typowe opóźnienia 200 ns i przepustowość 20 M wiadomości / sekundę. Wykorzystuje pliki mapowane w pamięci współdzielone między procesami (utrwala również dane, co stanowi najszybszy sposób na utrwalenie danych)

Peter Lawrey
źródło
7

Oto projekt zawierający testy wydajności dla różnych transportów IPC:

http://github.com/rigtorp/ipc-bench

sustrik
źródło
Nie zawiera „czynnika Java”, ale wygląda interesująco.
6

Jeśli kiedykolwiek rozważasz korzystanie z dostępu natywnego (ponieważ zarówno aplikacja, jak i „serwer” znajdują się na tej samej maszynie), weź pod uwagę JNA , ma mniej standardowego kodu, z którym musisz sobie poradzić.

bakkal
źródło
6

Spóźniony, ale chciał zwrócić uwagę na projekt open source poświęcony mierzeniu opóźnienia pingów przy użyciu Java NIO.

Dalsze zbadanie / wyjaśnienie w tym poście na blogu . Wyniki to (RTT w nanos):

Implementation, Min,   50%,   90%,   99%,   99.9%, 99.99%,Max
IPC busy-spin,  89,    127,   168,   3326,  6501,  11555, 25131
UDP busy-spin,  4597,  5224,  5391,  5958,  8466,  10918, 18396
TCP busy-spin,  6244,  6784,  7475,  8697,  11070, 16791, 27265
TCP select-now, 8858,  9617,  9845,  12173, 13845, 19417, 26171
TCP block,      10696, 13103, 13299, 14428, 15629, 20373, 32149
TCP select,     13425, 15426, 15743, 18035, 20719, 24793, 37877

Jest to zgodne z przyjętą odpowiedzią. Błąd System.nanotime () (szacowany przez nic nie mierzenie) jest mierzony na poziomie około 40 nanos, więc dla IPC rzeczywisty wynik może być niższy. Cieszyć się.

Nitsan Wakart
źródło
2

Nie wiem zbyt wiele o natywnej komunikacji między procesami, ale myślę, że musisz komunikować się za pomocą natywnego kodu, do którego można uzyskać dostęp za pomocą mechanizmów JNI. Tak więc z Java można wywołać natywną funkcję, która komunikuje się z innym procesem.

ryba
źródło
0

Czy rozważałeś pozostawienie otwartych gniazd, aby połączenia można było ponownie wykorzystać?

Thorbjørn Ravn Andersen
źródło
gniazda pozostają otwarte. połączenie jest aktywne przez cały czas działania aplikacji (około 7 godzin). wiadomości są wymieniane mniej więcej w sposób ciągły (powiedzmy około 5 do 10 na sekundę). obecne opóźnienie wynosi około 200 mikrosekund, celem jest ogolenie o 1 lub 2 rzędy wielkości.
Bastien
Opóźnienie 2 ms? Ambitny. Czy byłoby wykonalne przepisanie rzeczy C do biblioteki współdzielonej, z którą możesz się połączyć za pomocą JNI?
Thorbjørn Ravn Andersen
2 ms to 2000 mikrosekund, a nie 200. To sprawia, że ​​2 ms są znacznie mniej ambitne.
thewhiteambit
-1

Raport o błędzie Oracle dotyczący wydajności JNI: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4096069

JNI to powolny interfejs, dlatego gniazda Java TCP są najszybszą metodą powiadamiania między aplikacjami, jednak nie oznacza to, że musisz przesyłać ładunek przez gniazdo. Użyj LDMA do przeniesienia ładunku, ale jak wskazywały poprzednie pytania , obsługa mapowania pamięci w Javie nie jest idealna i dlatego będziesz chciał zaimplementować bibliotekę JNI do uruchamiania mmap.

Steve-o
źródło
3
Dlaczego JNI działa wolno? Zastanów się, jak działa niskopoziomowa warstwa TCP w Javie, nie jest napisana w kodzie bajtowym Javy! (Np. Musi to przejść przez natywnego hosta). Dlatego odrzucam twierdzenie, że gniazda Java TCP są szybsze niż JNI. (JNI jednak nie jest IPC.)
4
Pojedyncze połączenie z JNI kosztuje 9ns (na Intel i5), jeśli używasz tylko prymitywów. Więc nie jest tak wolno.
Martin Kersten