Здравствуйте, neFormal, Вы писали:
N>>2. Erlang имеет ряд тяжёлых специфических болезней. Например, неуправляемый входной поток данных — убийство VM. N>>На практике против последствий этого строят сложные системы управления. F>это как "неуправляемый"?
Процесс не может регулировать, что ему отправляется в mailbox. Mailbox один для сообщений всех типов и источников. При его большой длине синхронные взаимодействия начинают стоить пропорционально длине очереди каждое, а в сумме получается квадратичная зависимость. Некоторые действия даже по избавлению от данных (как gen_tcp:send) требуют обратного приёма сообщения, соответственно, там получается то же O(n^2).
F>и о каких системах речь?
Лев Валкин (lionet@), например, строил демпферы на точках передачи между нодами, оповещая их о состоянии приёмных процессов. У него целый фреймворк был для этого.
N>>Или куча процесса — если процесс толстый, можно не суметь её собрать — это к тому, что глобальный GC может быть даже лучше, чем на отдельный внутренний процесс.
F>а тут что помешает? F>gc же на каждый шедулер, а не на процесс.
В Erlang — по умолчанию на процесс, кроме общей кучи на толстые бинари. При запуске GC процесса должно быть достаточно свободной памяти, чтобы сделать копию всех выживших данных. Отсюда конструкции с небольшим количеством очень толстых процессов имеют шанс убить целиком еноду.
Здравствуйте, chaotic-kotik, Вы писали:
N>>1. Erlang VM не умеет статическую типизацию. На практике для задач, на которые целится Go, это даёт проигрыш до порядка по скорости. CK>Для критичных к производительности участков есть NIF-ы и порты.
Есть. Что означает, что отказываемся от всех преимуществ managed устройства и приходим к голому C.
N>>2. Erlang имеет ряд тяжёлых специфических болезней. Например, неуправляемый входной поток данных — убийство VM. Это проблема VM в целом, Elixir это не вылечит. На практике против последствий этого строят сложные системы управления. Или куча процесса — если процесс толстый, можно не суметь её собрать — это к тому, что глобальный GC может быть даже лучше, чем на отдельный внутренний процесс.
CK>А можно подробней, я не понял о чем идет речь.
Я вообще-то об этом писал кучу раз, но если хочется повторения — см. предыдущий мой постинг в эту тему.
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, VTT, Вы писали:
CK>>>Может кто-нибудь объяснить, почему люди считают что Go подходит для concurrency и сервисов, хотя там нет нормального планировщика (любая горутина может неотдавать управление неограничено долго), общая память и глобальный GC, невозможно нормально остановить горутину, нет нормальной обработки ошибок?
VTT>>Так гугл же форсит его, как только можно.
S>Например? Языку шесть лет как минимум, особо про него ничего не слышно. Сообщество у него не очень больше. В самом то гугле на нем что написано?
т.е. как это не слышно? Вот в соседнем треде обсуждают, как го стал языком 2016 года. Компилятор go вошел в gcc. Вакансии есть.
А вот об эраланге как раз действительно мало что слышно.
CK>>>Самое главное, есть же нормальная алтернатива — Erlang/Elixir + OTP.
VTT>>go мне представляется скорее альтернативой джаве, только с еще более низкими требованиями к кодерам.
S>Куда уж ниже требования? Go явно не альтернатива жабе, скорее язык где мн-во многопоточных идиом изкаропки. Он больше конкурент erlang'у.
Какое множество? Запустил горутину, прокачал канал и готово.
т.е. средства там есть, но среднестатистическому го-разработчику они вряд ли понадобится.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Здравствуйте, netch80, Вы писали:
N>* Стандартное "сегодня потребности в колбасе нет" на наследование, исключения и дженерики.
Это язык от сишников для сишников, у которых есть потребности в многозадачности. Им вот это вот ООП не требуется.
N>* Из того, что для моих целей крайне критично — нет приоритетов в select. Наоборот, явно прописан случайный выбор среди доступных — как по мне, это преступление. Случайный выбор должен быть дополнительным режимом, но не по умолчанию, и в идеале — в выделенной подгруппе случаев.
Вроде приоритет в порядке следования case'ов?
N>* Народ делает закат солнца вручную написанием своего ассемблера. Его качество резко хуже доступных аналогов вроде gcc и llvm.
Здравствуйте, Sharov, Вы писали:
N>>* Стандартное "сегодня потребности в колбасе нет" на наследование, исключения и дженерики. S>Это язык от сишников для сишников, у которых есть потребности в многозадачности. Им вот это вот ООП не требуется.
N>>* Из того, что для моих целей крайне критично — нет приоритетов в select. Наоборот, явно прописан случайный выбор среди доступных — как по мне, это преступление. Случайный выбор должен быть дополнительным режимом, но не по умолчанию, и в идеале — в выделенной подгруппе случаев. S>Вроде приоритет в порядке следования case'ов?
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
N>>* Народ делает закат солнца вручную написанием своего ассемблера. Его качество резко хуже доступных аналогов вроде gcc и llvm. S>Не понял об чем речь, какой ассемблер?
Хотя бы вот. Так вот, кодогенерация там очень слабая, если сравнивать с аналогами, где на заточку под процессоры с их возможностями ушло много человеко-лет. Но сделать выхлоп, например, в LLVM IR они не хотят — "у нас всё должно быть на Go".
Здравствуйте, netch80, Вы писали:
N>Здравствуйте, Sharov, Вы писали:
N>>>* Стандартное "сегодня потребности в колбасе нет" на наследование, исключения и дженерики. S>>Это язык от сишников для сишников, у которых есть потребности в многозадачности. Им вот это вот ООП не требуется.
N>>>* Из того, что для моих целей крайне критично — нет приоритетов в select. Наоборот, явно прописан случайный выбор среди доступных — как по мне, это преступление. Случайный выбор должен быть дополнительным режимом, но не по умолчанию, и в идеале — в выделенной подгруппе случаев. S>>Вроде приоритет в порядке следования case'ов?
N>Ну прочтите же вы документацию. N>
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
Да, вот с этим перепутал:
For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement.
Ну если для Вас это приоритетно, то делайте select на отдельном канале. Если не ошибаюсь, люди просто скопировали никсовый select-poll. Там вроде тоже нет приоритетов, хотя не уверен.
Здравствуйте, netch80, Вы писали:
N>Процесс не может регулировать, что ему отправляется в mailbox. Mailbox один для сообщений всех типов и источников. При его большой длине синхронные взаимодействия начинают стоить пропорционально длине очереди каждое, а в сумме получается квадратичная зависимость. Некоторые действия даже по избавлению от данных (как gen_tcp:send) требуют обратного приёма сообщения, соответственно, там получается то же O(n^2).
ну да, но чтобы это было убийством — ну фиг знает.
N>Лев Валкин (lionet@), например, строил демпферы на точках передачи между нодами, оповещая их о состоянии приёмных процессов. У него целый фреймворк был для этого.
ага, дистрибушон — слабое место.
N>В Erlang — по умолчанию на процесс, кроме общей кучи на толстые бинари. При запуске GC процесса должно быть достаточно свободной памяти, чтобы сделать копию всех выживших данных. Отсюда конструкции с небольшим количеством очень толстых процессов имеют шанс убить целиком еноду.
откуда там отдельные gc?
то, что процессы отдают память лишь в двух случаях передачи управления другим процессам, не значит, что у каждого по отдельному gc.
просто при передаче управления они выкидывают уже ненужное. и "общая куча" при этом тоже имеет возможность избавиться от лишнего.
Здравствуйте, Sharov, Вы писали:
S>Ну если для Вас это приоритетно, то делайте select на отдельном канале. Если не ошибаюсь, люди просто скопировали никсовый select-poll. Там вроде тоже нет приоритетов, хотя не уверен.
"Там" пользователь API получает список всех готовых на момент вызова и сам выбирает, как ему строить приоритеты. (И этим пользуются.) В Go аналога этому нет, идёт сразу переход на выбранную рантаймом ветку.
Здравствуйте, neFormal, Вы писали:
N>>Процесс не может регулировать, что ему отправляется в mailbox. Mailbox один для сообщений всех типов и источников. При его большой длине синхронные взаимодействия начинают стоить пропорционально длине очереди каждое, а в сумме получается квадратичная зависимость. Некоторые действия даже по избавлению от данных (как gen_tcp:send) требуют обратного приёма сообщения, соответственно, там получается то же O(n^2). F>ну да, но чтобы это было убийством — ну фиг знает.
Ну мне сложно подобрать иной термин, как "убийство проекта", для того, что мне пришлось выкинуть Erlang-версию и срочно перегнать приблуду (специализированный прокси) на C++.
Появился гимор с управлением памятью, зато исчез — с неуправляемыми заклинами всего приложения под нагрузкой.
N>>Лев Валкин (lionet@), например, строил демпферы на точках передачи между нодами, оповещая их о состоянии приёмных процессов. У него целый фреймворк был для этого. F>ага, дистрибушон — слабое место.
Там был не то чтобы distribution...
N>>В Erlang — по умолчанию на процесс, кроме общей кучи на толстые бинари. При запуске GC процесса должно быть достаточно свободной памяти, чтобы сделать копию всех выживших данных. Отсюда конструкции с небольшим количеством очень толстых процессов имеют шанс убить целиком еноду.
F>откуда там отдельные gc? F>то, что процессы отдают память лишь в двух случаях передачи управления другим процессам, не значит, что у каждого по отдельному gc.
Не значит, но факт тот, что там отдельные кучи у каждого процесса.
F>просто при передаче управления они выкидывают уже ненужное. и "общая куча" при этом тоже имеет возможность избавиться от лишнего.
Нет. GC срабатывает в каждом процессе отдельно и по превышению им количества так называемых reductions с прошлого старта GC. Reduction — это, грубо говоря, любая элементарная операция интерпретатора (один оператор выражения, один вызов функции, один простой матчинг-биндинг и т.п.) В последних релизах разделены поколения и полная сборка, но по-прежнему по процессам раздельно. Читайте начиная с process_flag(fullsweep_after).
Здравствуйте, netch80, Вы писали:
N>Ну мне сложно подобрать иной термин, как "убийство проекта", для того, что мне пришлось выкинуть Erlang-версию и срочно перегнать приблуду (специализированный прокси) на C++. N>Появился гимор с управлением памятью, зато исчез — с неуправляемыми заклинами всего приложения под нагрузкой.
это до следующего уровня нагрузки.
дропать сообщеньки надо. ну, если нет больших проблем с постановкой сообщенек самому себе, тогда совсем всё плохо.
N>>>Лев Валкин (lionet@), например, строил демпферы на точках передачи между нодами, оповещая их о состоянии приёмных процессов. У него целый фреймворк был для этого. F>>ага, дистрибушон — слабое место. N>Там был не то чтобы distribution...
а зачем тогда? выравнивал нагрузку между нодами?
N>Не значит, но факт тот, что там отдельные кучи у каждого процесса.
да как бы и нет. если нужна память, процесс её берёт у VM, которая берёт её у системы.
память берётся чанками и освобождается в паре ситуаций.
F>>просто при передаче управления они выкидывают уже ненужное. и "общая куча" при этом тоже имеет возможность избавиться от лишнего. N>Нет. GC срабатывает в каждом процессе отдельно и по превышению им количества так называемых reductions с прошлого старта GC. Reduction — это, грубо говоря, любая элементарная операция интерпретатора (один оператор выражения, один вызов функции, один простой матчинг-биндинг и т.п.) В последних релизах разделены поколения и полная сборка, но по-прежнему по процессам раздельно. Читайте начиная с process_flag(fullsweep_after).
да что-то ни разу не так.
память чистится либо при завершении процесса, либо с hibernate из gen_server'а.
Здравствуйте, netch80, Вы писали:
N>Процесс не может регулировать, что ему отправляется в mailbox. Mailbox один для сообщений всех типов и источников. При его большой длине синхронные взаимодействия начинают стоить пропорционально длине очереди каждое, а в сумме получается квадратичная зависимость. Некоторые действия даже по избавлению от данных (как gen_tcp:send) требуют обратного приёма сообщения, соответственно, там получается то же O(n^2).
Я вот как-то не очень понимаю (совсем не) как получается O(n2? Если процесс А отправил сообщение процессу В, а у того mailbox забит и он его разгребвает, то количество сообщений, которые он разгребет до того как отправить ответ процессу А, действительно зависит от количества сообщений в mailbox-е. Но он ведь в любом случае должен все эти сообщения обработать, не?
Я думал что там проблема в том, что процессу могут накидать столько, что он грохнется по OOM. Но это by design. Если делать блокирующие ограниченые очереди как в Go, то непонятно как это должно быть реализовано, если процесс на другой машине.
Здравствуйте, netch80, Вы писали:
N>Здравствуйте, Sharov, Вы писали:
S>>Ну если для Вас это приоритетно, то делайте select на отдельном канале. Если не ошибаюсь, люди просто скопировали никсовый select-poll. Там вроде тоже нет приоритетов, хотя не уверен.
N>"Там" пользователь API получает список всех готовых на момент вызова и сам выбирает, как ему строить приоритеты. (И этим пользуются.) В Go аналога этому нет, идёт сразу переход на выбранную рантаймом ветку.
Тут согласен. Я думаю они все прекрасно понимали, и рассчитывали что вне циклов использовать select смысла нет, поэтому приоритетное событие никуда не убежит. Имело ли смысли усложнять язык ради метки приоритета -- . Они посчитали, что нет. С другой стороны в выше процитированной док-ии сказано, что выч. на канале идут в порядке следования их case'ов. И как бэ неявно приоритет будет отдаваться первому, наверное. В целом было логично, что если первый case готов -- управление ему, иначе рандомный выбор.
Здравствуйте, netch80, Вы писали:
N>>>Процесс не может регулировать, что ему отправляется в mailbox. Mailbox один для сообщений всех типов и источников. При его большой длине синхронные взаимодействия начинают стоить пропорционально длине очереди каждое, а в сумме получается квадратичная зависимость. Некоторые действия даже по избавлению от данных (как gen_tcp:send) требуют обратного приёма сообщения, соответственно, там получается то же O(n^2). F>>ну да, но чтобы это было убийством — ну фиг знает.
N>Ну мне сложно подобрать иной термин, как "убийство проекта", для того, что мне пришлось выкинуть Erlang-версию и срочно перегнать приблуду (специализированный прокси) на C++. N>Появился гимор с управлением памятью, зато исчез — с неуправляемыми заклинами всего приложения под нагрузкой.
На Эрланге я не писал ничего за пределами тестового задания для конторы уже упомянутого @lionet, но мне интересно — если это у вас был прокси, то неужто нельзя было просто раскидывать запросы по нескольким узлам?
Здравствуйте, 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 уже многолетний. Перенести его не проблема.
Здравствуйте, netch80, Вы писали:
N>Удобный пример — конвертилка чего-то внутреннего формата в сообщения протокола поверх TCP и отправлялка в мир. N>На вход поступило N сообщений. Выбрали первое, сериализовали, вызвали отправку через gen_tcp:send(). Та через несколько уровней дошла до вызова порта и ожидания сообщения в ответ(!) о том, что порт отработал команду записи. N>В это время процесс сидит в receive. Чтобы перебрать всю очередь и перейти в ожидание, ему нужно прошерстить N-1 сообщение (одно мы только что забрали). Дальше он ждёт (или не ждёт, если ответ порта уже успел прийти). N>OK, осталось N-1. Та же процедура, пересканировать вход, пропустив N-2 сообщения. N>И так далее. N>То есть, если новых не поступит, время отработки пропорционально квадрату количества поступивших (точнее, каждый может подсчитать, чему равно N*(N-1)+(N-1)*(N-2)+(N-2)*(N-3)+..., но мне облом — асимпотически это равно N*N/2).
Т.е. это все в том случае, если используется selective receive. Тогда понятно, спасибо за разъяснения.
Здравствуйте, chaotic-kotik, Вы писали:
CK>Т.е. это все в том случае, если используется selective receive. Тогда понятно, спасибо за разъяснения.
Ну да. Проблема в том, что оно используется дофига где и в самых ключевых механизмах, так что даже искоренив его в своём коде, можно получить такое же неожиданно в библиотечной функции.
Здравствуйте, neFormal, Вы писали:
N>>Ну мне сложно подобрать иной термин, как "убийство проекта", для того, что мне пришлось выкинуть Erlang-версию и срочно перегнать приблуду (специализированный прокси) на C++. N>>Появился гимор с управлением памятью, зато исчез — с неуправляемыми заклинами всего приложения под нагрузкой. F>это до следующего уровня нагрузки. F>дропать сообщеньки надо. ну, если нет больших проблем с постановкой сообщенек самому себе, тогда совсем всё плохо.
Специфика — я не имею право ничего дропать. Могу разве что убить соединение целиком. Но после реконнекта пойдёт всё заново литься, с наверняка тем же результатом.
N>>>>Лев Валкин (lionet@), например, строил демпферы на точках передачи между нодами, оповещая их о состоянии приёмных процессов. У него целый фреймворк был для этого. F>>>ага, дистрибушон — слабое место. N>>Там был не то чтобы distribution... F>а зачем тогда? выравнивал нагрузку между нодами?
Просто регулировал, чтобы они не умерли. А так — я уже очень плохо помню, но были фронтэнды и разные бэкэнды.
N>>Не значит, но факт тот, что там отдельные кучи у каждого процесса. F>да как бы и нет. если нужна память, процесс её берёт у VM, которая берёт её у системы. F>память берётся чанками и освобождается в паре ситуаций.
Да. Включая full GC.
F>>>просто при передаче управления они выкидывают уже ненужное. и "общая куча" при этом тоже имеет возможность избавиться от лишнего. N>>Нет. GC срабатывает в каждом процессе отдельно и по превышению им количества так называемых reductions с прошлого старта GC. Reduction — это, грубо говоря, любая элементарная операция интерпретатора (один оператор выражения, один вызов функции, один простой матчинг-биндинг и т.п.) В последних релизах разделены поколения и полная сборка, но по-прежнему по процессам раздельно. Читайте начиная с process_flag(fullsweep_after).
F>да что-то ни разу не так. F>память чистится либо при завершении процесса, либо с hibernate из gen_server'а.
Или на ходу работы. Я не знаю, как ты этого не видишь, можно элементарным образом показать.
Может, у тебя были просто короткоживущие процессы. У меня в нескольких крайне важных случаях был пул долгоживущих.
Здравствуйте, Слава, Вы писали:
N>>Ну мне сложно подобрать иной термин, как "убийство проекта", для того, что мне пришлось выкинуть Erlang-версию и срочно перегнать приблуду (специализированный прокси) на C++. N>>Появился гимор с управлением памятью, зато исчез — с неуправляемыми заклинами всего приложения под нагрузкой.
С>На Эрланге я не писал ничего за пределами тестового задания для конторы уже упомянутого @lionet, но мне интересно — если это у вас был прокси, то неужто нельзя было просто раскидывать запросы по нескольким узлам?
Специфика задачи — нельзя ничего раскидывать. Да и не сильно оно бы помогло.
Здравствуйте, netch80, Вы писали:
N>На вход поступило N сообщений. Выбрали первое, сериализовали, вызвали отправку через gen_tcp:send(). Та через несколько уровней дошла до вызова порта и ожидания сообщения в ответ(!) о том, что порт отработал команду записи.
... N>В одну очередь это не лечится. Нужно делать много. И по умолчанию разделять очередь обычной посылки и очередь ответов на синхронные запросы.
Да, неприятно, но на первый взгляд лечится и довольно тривиально. Но может и у этих решений есть какая-то проблема — я не знаю, я на практике с неконтролируемой отправкой не сталкивался.
N>И подобные ситуации не только с TCP, а с любым случаем, где владелец длинной очереди синхронно с кем-то общается (то есть, дожидаясь ответа). N>(Да, оптимизации R14 про watermark на эту очередь не вспоминать — они тут не помогают.)
С портами не помогают, но почему не помогают "с любым случаем, где владелец длинной очереди синхронно с кем-то общается"? И почему — для этого любого другого случая — не вспоминать про watermark?
(для ясности контекста я позволил себе переставить цитируемое местами, надеюсь что без искажения смысла)
Здравствуйте, netch80, Вы писали:
N>Или на ходу работы. Я не знаю, как ты этого не видишь, можно элементарным образом показать.
вот по ходу работы не видел, да.
бинарные строки уж точно не удаляются по ходу дела, это я прочувствовал в полной мере.
афаир в `erlang in anger` это всё описано хорошо.
N>Может, у тебя были просто короткоживущие процессы. У меня в нескольких крайне важных случаях был пул долгоживущих.
наоборот, в основном длинные. это постоянные коннекты юзеров с толстым state'ом и несколько важных подсистем не менее худых.
это при коротких незаметно, при длинных память улетает просто на ура. тем более у меня очень много работы было завязано как раз на строки. я наступил, наверное, на все грабли.