Comments 23
Если сообщения короткие, то udp предпочтительнее tcp.
Да, но тогда строго говоря надо самостоятельно следить за потерями пакетов и что-то предпринимать по этому поводу (мы ж не можем просто "забить" на непришедший ответ).
Понятно, что в рамках одной машины пакеты скорее всего теряться не будут, но строго говоря никто этого не гарантирует :-)
Строго говоря, не нужно на одной машине совмещать несколько вещей, таких как веб-сервис и какой-то микросеривис.
Да и ваш тестовый пример возможно запустить только на PHP 7.4, который уже месяца 3-4 как EOL.
Не соглашусь, в некоторых случаях бывает оправдано размещение именно на одной машине. В частности, когда мы хотим существенно более быстрое взаимодействие между этими сервисами. Понятно, что кейс наверное в текущих реалиях не самый частый, но тем не менее.
Тестовый пример на то и тестовый, переписать на 8 версию не так сложно (тем более, что сборку расширения под 8.2 я уже починил).
Спасибо за статью, сейчас как раз пишу то же самое, только в виде генератора ).
Смущает получившаяся у вас скорость, всего 30 тысяч запросов в секунду.
Это следствие следствие сложной обработки запроса сервером?
В тестовом сервере обработка заключается в банальном проходе по строке и подсчёту числа символов с разными кодами. Потому вряд ли в скорости обработки дело.
Возможно сказывается время на первичную установку коннекта. Для интереса прогнал тест с 1000000 запросов, результаты вот:
Testing 1000000 requests, reconnect after 1000000 size 128
Testing shared memory channel:
Total: 20.809 seconds, 48055.132 RPS
Testing 1000000 requests, reconnect after 1000000 size 2048
Testing shared memory channel:
Total: 217.817 seconds, 4591.005 RPS
Странно себя ведёт решение с TCP сокетами - при постоянных переподключениях работает даже лучше, чем при переиспользовании соединения.
Вероятно это срабатывает алгоритм Нейгла. Можно попробовать запустить тест без него. В PHP для этого есть socket_set_option и флаг TCP_NODELAY.
Тоже об этом думал. Добавил вариант с этой опцией, но результаты не поменялись.
Testing 1000 requests, reconnect after 1000 size 128
Testing IP socket:
Total: 28.123 seconds, 35.558 RPS
Testing IP socket with TCP_NODELAY:
Total: 28.151 seconds, 35.523 RPS
Testing Unix socket:
Total: 10.174 seconds, 98.294 RPS
Testing shared memory channel:
Total: 0.039 seconds, 25698.345 RPS
Testing 1000 requests, reconnect after 1 size 128
Testing IP socket:
Total: 10.303 seconds, 97.057 RPS
Testing IP socket with TCP_NODELAY:
Total: 10.306 seconds, 97.029 RPS
Testing Unix socket:
Total: 10.203 seconds, 98.009 RPS
Testing shared memory channel:
Total: 10.396 seconds, 96.191 RPS
Да, оказалось, что именно в нём дело https://habr.com/ru/post/724682/.
TL;DR: NODELAY надо с обеих сторон выставлять.
Для TCP проблематично ускоряться при переподключениях: накладных расходов много, также на сервере создаётся каждый раз поток. Предположил бы, что здесь сильным ограничителем выступает клиентская сторона (плюс синхронный обмен).
Проверить это можно, например, получив tcpdump. Или написав клиента на C++.
Также согласен с pae174: похоже у вас nodelay не отрабатывает. Для проверки попробуйте пакет (в обе стороны) разбивать на собственно длину (отдельно посылается 4 байта) и собственно данные. И смотрите в tcp дамп.
Пожалуй, это стоит отдельно поизучать. Займусь как-нибудь на досуге, самому интересно, странный очень эффект.
Я разобрался https://habr.com/ru/post/724682/. Дело действительно в Nagle.
TL;DR: NODELAY надо выставлять с обеих сторон. Сочетание write+write+read - проблемное, ненадотак.
Если блок памяти общий, замапленный на адресное пространство, его можно использовать приложением напрямую, т.е. можно было бы создать объект в памяти одновременно доступный в двух приложениях. Сам факт сообщения мог бы нести только ссылку на этот объект в памяти — 4..8 байта.
p.s. вопрос, как работает shm_get_var в php?
Имеете в виду, положить php-объект сразу в расшаренную память? Звучит интересно. Чисто технически, можно написать расширение, которое будет создавать объекты (ну, хотя бы строки), которые сразу доступны из обоих процессов.
Про shm_get_var не в курсе, но вполне возможно она делает именно то, что вы описываете.
Да, когда я писал расширения к Perl, там у меня как раз была сборка Perl-объекта на стороне C++ и возврат его в Perl целиком. Единственное неудобство - так как в Perl сборщик мусора основан на ссылках (refcount), надо очень аккуратно за ними следить, я в какой-то момент долго разбирался, почему память утекает.
p.s. вопрос, как работает shm_get_var в php?
Эта штука вначале пропускает сдвиг для заголовка, потом переходит по нужному смещению (на основе индекса переменной, смещение следующего элемента записано в каждом элементе), а потом вытаскивает нужные данные и десериализует их. Таким образом, для N переменной формата int64 требуется N считываний, а потом распаковка формата (например такого для 2го элемента со значением int(23)
):
02 00 00 00 00 00 00 00 // << Это идентификатор
05 00 00 00 00 00 00 00 // << Это размер данных (вроде бы о_0)
( 00 00 00 00 00 00 00
i : 2 3 ; 00 00 00 00 00 00 00 00 00 00 00 // << Это данные
Поэтому использование sysvshm — это самый медленный и неоптимальный способ работы с SystemV, в отличие, например, от всех остальных решений (sysvshm vs. shmop vs. sync vs. ffi), которые предоставляют прямой доступ к памяти как к файлу: https://gist.github.com/SerafimArts/4c8cd05f20384551a65e3a2958557028
+------------+------------+-----+--------+-----+-----------+---------+--------+
| benchmark | subject | set | revs | its | mem_peak | mode | rstdev |
+------------+------------+-----+--------+-----+-----------+---------+--------+
| WriteBench | benchShm | | 100000 | 5 | 472.232kb | 0.338μs | ±3.09% |
| WriteBench | benchShmop | | 100000 | 5 | 472.232kb | 0.174μs | ±2.25% |
| WriteBench | benchSync | | 100000 | 5 | 472.232kb | 0.158μs | ±1.51% |
| WriteBench | benchFFI | | 100000 | 5 | 472.232kb | 0.154μs | ±1.41% |
+------------+------------+-----+--------+-----+-----------+---------+--------+
P.S. Если же говорить про возможность просто сложить указатель в shmop, то чисто технически это возможно, т.к. и в fcgi и в mod_php память у всех доступна между процессами/потоками (т.е. никакого SIGSEGV и проч. не должно случиться). Т.е. достаточно на посылаемой стороне взять этот указатель, а на получаемой восстановить его в executor_globals. Однако стоит учитывать, что GC на "обеих сторонах" свой собственный, так что в момент refcount = 0
можно словить гонку состояний. Даже если не учитывать то, что любые операции с этим объектом будут подвержены сабжевой ошибке. Да и refcount тоже без атомиков работает вроде как)))
P.S. Вот более точные бенчмарки со всеми возможными (что придумалось) способами работы с памятью https://github.com/SerafimArts/SharedMemoryBench
И совершенно логичным образом всех заруливает "ручная" работа с памятью через FFI :-)
В основном потому что там вырезаны вообще все проверки, которые есть в оригинальном коде.
1) Вот оригинал, а вот жалкая пародия
2) Ну и запись: Неповторимый оригинал и жалкая пародия
А так да, через FFI можно добиться производительности выше, если знаешь что делаешь (ну кроме APCu, он там вон ещё быстрее по бенчам если приводить данные к типам, но я не копал, вряд ли он SysV использует, вполне возможно mmap какой-нибудь).
APCu я бы тут отнёс к читерству, потому что добраться из не-php процесса до этого самого APCu будет проблемно.
Да, почитал, там внутрях SysV https://github.com/krakjoe/apcu/blob/master/apc_shm.c
Помимо IPC есть еще и SHM (Named Shared Memory)... И, кстати, на win - тоже (уж не знаю, какую ботву в M$ курили...)
POSIX стандарт.
Взаимодействие между процессами на С++ и PHP. Сокеты, семафоры и разделяемая память