самый быстрый (с низкой задержкой) метод межпроцессного взаимодействия между Java и C / C ++


100

У меня есть приложение Java, которое через сокет TCP подключается к «серверу», разработанному на C / C ++.

и приложение, и сервер работают на одном компьютере, в системе Solaris (но в конечном итоге мы рассматриваем возможность перехода на Linux). Тип обмена данными - простые сообщения (логин, авторизация ACK, затем клиент что-то запрашивает, ответы сервера). каждое сообщение имеет длину около 300 байт.

В настоящее время мы используем сокеты, и все в порядке, однако я ищу более быстрый способ обмена данными (меньшая задержка) с использованием методов IPC.

Я исследовал сеть и нашел ссылки на следующие технологии:

  • Общая память
  • трубы
  • очереди
  • а также то, что называется DMA (прямой доступ к памяти)

но я не смог найти ни надлежащего анализа их соответствующих характеристик, ни того, как реализовать их как на JAVA, так и на C / C ++ (чтобы они могли разговаривать друг с другом), за исключением, возможно, каналов, которые я мог представить, как это сделать.

может ли кто-нибудь прокомментировать производительность и осуществимость каждого метода в этом контексте? любой указатель / ссылка на полезную информацию о реализации?


ИЗМЕНИТЬ / ОБНОВИТЬ

следуя комментариям и ответам, которые я получил здесь, я нашел информацию о сокетах домена Unix, которые, похоже, построены прямо над каналами и спасут мне весь стек TCP. он специфичен для платформы, поэтому я планирую протестировать его с помощью JNI или с помощью juds или junixsocket .

Следующими возможными шагами будет прямая реализация каналов, а затем общая память, хотя меня предупреждали о дополнительном уровне сложности ...


Спасибо за вашу помощь


7
В вашем случае это может быть излишним, но рассмотрите zeromq.org
jfs

это интересно, однако идея состояла бы в том, чтобы сначала использовать "общие" (как в ОС или на языке) методы, поэтому я упомянул очереди и разделяемую память.
Bastien


Не забудьте сопоставленные файлы или просто UDP.

10
UDP медленнее TCP ??? хммм ... доказательства, пожалуйста
Боппити Боп

Ответы:


103

Только что протестировали задержку с Java на моем Corei5 2,8 ГГц, только однобайтовые отправка / получение, 2 процесса Java только что порождены, без назначения конкретных ядер ЦП с набором задач:

TCP         - 25 microseconds
Named pipes - 15 microseconds

Теперь явно указываем основные маски, например набор задач 1 java Srv или набор задач 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 !!!!

так

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

В то же время Thread.sleep (0) (который, как показывает strace, вызывает выполнение одного вызова ядра Linux sched_yield ()), занимает 0,3 микросекунды, поэтому именованные каналы, запланированные на одно ядро, по-прежнему имеют много накладных расходов.

Некоторые измерения совместно используемой памяти: 14 сентября 2009 г. - Сегодня Solace Systems объявила, что ее API платформы единой системы обмена сообщениями может достигать средней задержки менее 700 наносекунд с использованием транспорта совместно используемой памяти. http://solacesystems.com/news/fastest-ipc-messaging/

PS - попробовал разделяемую память на следующий день в виде файлов с отображением памяти, если занятое ожидание приемлемо, мы можем уменьшить задержку до 0,3 микросекунды для передачи одного байта с помощью следующего кода:

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
}

Примечания: Thread.sleep (0) необходим, чтобы 2 процесса могли видеть изменения друг друга (другого способа я пока не знаю). Если 2 процесса принудительно подключены к одному ядру с помощью набора задач, задержка становится 1,5 микросекунды - это задержка переключения контекста

PPS - и 0,3 микросекунды - хорошее число! Следующий код занимает ровно 0,1 микросекунды при выполнении только простой конкатенации строк:

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

PPPS - надеюсь, это не слишком уж не по теме, но, наконец, я попытался заменить Thread.sleep (0) на инкремент статической изменчивой переменной int (при этом JVM сбрасывает кеши ЦП) и получил - запись! - Связь между процессами Java-Java с задержкой 72 наносекунды !

Однако при принудительном использовании одного и того же ядра ЦП JVM с изменяющимся инкрементом никогда не уступают управление друг другу, таким образом создавая задержку ровно 10 миллисекунд - квант времени Linux кажется 5 мс ... Так что это следует использовать только при наличии запасного ядра - в противном случае sleep (0) безопаснее.


спасибо Andriy, очень информативное исследование, и оно более или менее соответствует моим измерениям для TCP, так что это хорошая ссылка. Думаю, я посмотрю на именованные каналы.
Bastien

Таким образом, замена Thread (Sleep) на увеличение volatile static int должна выполняться только в том случае, если вы можете закрепить процесс на разных ядрах? Кроме того, я не знал, что ты можешь это сделать? Думал ОС решает?
мезаморфный

3
Попробуйте LockSupport.parkNanos (1), он должен сделать то же самое.
reccles

Очень хорошо. Вы можете сделать лучше (как в случае задержки RTT 5-7us) для пинга TCP. См. Здесь: psy-lob-saw.blogspot.com/2012/12/…
Nitsan Wakart

1
Дальнейшее исследование использования файла с отображением памяти в качестве разделяемой памяти для поддержки очереди IPC в Java: psy-lob-saw.blogspot.com/2013/04/lock-free-ipc-queue.html с достижением 135M сообщений в секунду. Также см. Мой ответ ниже для сравнительного исследования задержки по методам.
Nitsan Wakart

10

DMA - это метод, с помощью которого аппаратные устройства могут получать доступ к физической памяти, не прерывая работу ЦП. Например, типичным примером является контроллер жесткого диска, который может копировать байты прямо с диска в RAM. Таким образом, это не применимо к IPC.

Совместно используемая память и каналы напрямую поддерживаются современными ОС. Таким образом, они довольно быстрые. Очереди обычно являются абстракциями, например, реализованными поверх сокетов, каналов и / или общей памяти. Это может выглядеть как более медленный механизм, но альтернативой является то , что вы создать такую абстракцию.


для DMA, почему тогда я могу читать много вещей, связанных с RDMA (как удаленный прямой доступ к памяти), которые будут применяться по сети (особенно с InfiniBand), и делать то же самое. Я на самом деле пытаюсь достичь эквивалента БЕЗ сети (поскольку все находится в одном ящике).
Bastien

RDMA - это та же концепция: копирование байтов по сети без прерывания работы процессоров с обеих сторон. Он по-прежнему не работает на уровне процесса.
MSalters

10

Вопрос был задан некоторое время назад, но вас может заинтересовать https://github.com/peter-lawrey/Java-Chronicle, который поддерживает типичные задержки 200 нс и пропускную способность 20 M сообщений в секунду. Он использует файлы с отображением памяти, совместно используемые между процессами (он также сохраняет данные, что делает его самым быстрым способом сохранения данных)



6

Если вы когда-нибудь задумывались об использовании собственного доступа (поскольку и ваше приложение, и «сервер» находятся на одной машине), подумайте о JNA , у нее меньше шаблонного кода, с которым вам придется иметь дело.


6

Позднее прибытие, но хотелось бы отметить проект с открытым исходным кодом, посвященный измерению задержки ping с помощью Java NIO.

Более подробно исследовано / объяснено в этом сообщении в блоге . Результаты (RTT в нано):

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

Это соответствует принятому ответу. Ошибка System.nanotime () (оцениваемая по нулевым измерениям) составляет около 40 нанометров, поэтому для IPC фактический результат может быть ниже. Наслаждаться.


2

Я мало знаю о нативном межпроцессном взаимодействии, но предполагаю, что вам нужно общаться, используя собственный код, к которому вы можете получить доступ с помощью механизмов JNI. Итак, из Java вы должны вызвать встроенную функцию, которая разговаривает с другим процессом.



0

Рассматривали ли вы, чтобы сокеты оставались открытыми, чтобы соединения можно было использовать повторно?


розетки остаются открытыми. соединение активно все время работы приложения (около 7 часов). сообщения обмениваются более или менее непрерывно (скажем, от 5 до 10 в секунду). текущая задержка составляет около 200 микросекунд, цель - сократить на 1 или 2 порядка.
Bastien

Задержка 2 мс? Честолюбивый. Можно ли было бы переписать C-материал в общую библиотеку, к которой вы можете подключиться с помощью JNI?
Торбьёрн Равн Андерсен

2 мс - это 2000 микросекунд, а не 200. Это делает 2 мс гораздо менее амбициозными.
thewhiteambit

-1

Отчет об ошибке Oracle в производительности JNI: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4096069

JNI - медленный интерфейс, поэтому сокеты Java TCP являются самым быстрым методом уведомления между приложениями, однако это не означает, что вам нужно отправлять полезную нагрузку через сокет. Используйте LDMA для передачи полезной нагрузки, но, как указывалось в предыдущих вопросах , поддержка Java для отображения памяти не идеальна, и вы захотите реализовать библиотеку JNI для запуска mmap.


3
Почему JNI медленный? Подумайте, как работает низкоуровневый уровень TCP в Java, он не написан на байт-коде Java! (Например, это должно проходить через собственный хост.) Таким образом, я отвергаю утверждение, что сокеты Java TCP работают быстрее, чем JNI. (JNI, однако, не IPC.)

4
Один вызов JNI стоит 9 нс (на Intel i5), если вы используете только примитивы. Так что это не так уж и медленно.
Мартин Керстен
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.