Информация об изменениях

Сообщение Re[5]: Go vs Erlang vs Elixir от 09.02.2017 15:27

Изменено 09.02.2017 19:47 netch80

Re[5]: Go vs Erlang vs Elixir
Здравствуйте, chaotic-kotik, Вы писали:

N>>Процесс не может регулировать, что ему отправляется в mailbox. Mailbox один для сообщений всех типов и источников. При его большой длине синхронные взаимодействия начинают стоить пропорционально длине очереди каждое, а в сумме получается квадратичная зависимость. Некоторые действия даже по избавлению от данных (как gen_tcp:send) требуют обратного приёма сообщения, соответственно, там получается то же O(n^2).

CK>Я вот как-то не очень понимаю (совсем не) как получается O(n2? Если процесс А отправил сообщение процессу В, а у того mailbox забит и он его разгребвает, то количество сообщений, которые он разгребет до того как отправить ответ процессу А, действительно зависит от количества сообщений в mailbox-е. Но он ведь в любом случае должен все эти сообщения обработать, не?

Удобный пример — конвертилка чего-то внутреннего формата в сообщения протокола поверх TCP и отправлялка в мир.
На вход поступило N сообщений. Выбрали первое, сериализовали, вызвали отправку через gen_tcp:send(). Та через несколько уровней дошла до вызова порта и ожидания сообщения в ответ(!) о том, что порт отработал команду записи.
В это время процесс сидит в receive. Чтобы перебрать всю очередь и перейти в ожидание, ему нужно прошерстить N-1 сообщение (одно мы только что забрали). Дальше он ждёт (или не ждёт, если ответ порта уже успел прийти).
OK, осталось N-1. Та же процедура, пересканировать вход, пропустив N-2 сообщения.
И так далее.
То есть, если новых не поступит, время отработки пропорционально квадрату количества поступивших (точнее, каждый может подсчитать, чему равно N*(N-1)+(N-1)*(N-2)+(N-2)*(N-3)+..., но мне облом — асимпотически это равно N*N/2).

И подобные ситуации не только с TCP, а с любым случаем, где владелец длинной очереди синхронно с кем-то общается (то есть, дожидаясь ответа).
(Да, оптимизации R14 про watermark на эту очередь не вспоминать — они тут не помогают.)

В одну очередь это не лечится. Нужно делать много. И по умолчанию разделять очередь обычной посылки и очередь ответов на синхронные запросы.

CK>Я думал что там проблема в том, что процессу могут накидать столько, что он грохнется по OOM. Но это by design. Если делать блокирующие ограниченые очереди как в Go, то непонятно как это должно быть реализовано, если процесс на другой машине.


Можно просто не принять и продискардить. Если разрешено опциями посылки.
Можно придумать блокирующий межнодовый send для этого (а лучше — с таймаутом).
В конце концов, опыт UDP, TCP, SCTP уже многолетний. Перенести его не проблема.
Re[5]: Go vs Erlang vs Elixir
Здравствуйте, chaotic-kotik, Вы писали:

N>>Процесс не может регулировать, что ему отправляется в mailbox. Mailbox один для сообщений всех типов и источников. При его большой длине синхронные взаимодействия начинают стоить пропорционально длине очереди каждое, а в сумме получается квадратичная зависимость. Некоторые действия даже по избавлению от данных (как gen_tcp:send) требуют обратного приёма сообщения, соответственно, там получается то же O(n^2).

CK>Я вот как-то не очень понимаю (совсем не) как получается O(n2? Если процесс А отправил сообщение процессу В, а у того mailbox забит и он его разгребвает, то количество сообщений, которые он разгребет до того как отправить ответ процессу А, действительно зависит от количества сообщений в mailbox-е. Но он ведь в любом случае должен все эти сообщения обработать, не?

Удобный пример — конвертилка чего-то внутреннего формата в сообщения протокола поверх TCP и отправлялка в мир.
На вход поступило N сообщений. Выбрали первое, сериализовали, вызвали отправку через gen_tcp:send(). Та через несколько уровней дошла до вызова порта и ожидания сообщения в ответ(!) о том, что порт отработал команду записи.
В это время процесс сидит в receive. Чтобы перебрать всю очередь и перейти в ожидание, ему нужно прошерстить N-1 сообщение (одно мы только что забрали). Дальше он ждёт (или не ждёт, если ответ порта уже успел прийти).
OK, осталось N-1. Та же процедура, пересканировать вход, пропустив N-2 сообщения.
И так далее.
То есть, если новых не поступит, время отработки пропорционально квадрату количества поступивших (точнее, каждый может подсчитать, чему равно (N-1)+(N-2)+(N-3)+..., но мне облом — асимпотически это равно N*N/2). [UPD: исправил формулу]

И подобные ситуации не только с TCP, а с любым случаем, где владелец длинной очереди синхронно с кем-то общается (то есть, дожидаясь ответа).
(Да, оптимизации R14 про watermark на эту очередь не вспоминать — они тут не помогают.)

В одну очередь это не лечится. Нужно делать много. И по умолчанию разделять очередь обычной посылки и очередь ответов на синхронные запросы.

CK>Я думал что там проблема в том, что процессу могут накидать столько, что он грохнется по OOM. Но это by design. Если делать блокирующие ограниченые очереди как в Go, то непонятно как это должно быть реализовано, если процесс на другой машине.


Можно просто не принять и продискардить. Если разрешено опциями посылки.
Можно придумать блокирующий межнодовый send для этого (а лучше — с таймаутом).
В конце концов, опыт UDP, TCP, SCTP уже многолетний. Перенести его не проблема.