Технически, почему процессы в Erlang более эффективны, чем потоки ОС?

170

Характеристики Эрланга

Из Erlang Programming (2009):

Эрлангский параллелизм быстрый и масштабируемый. Его процессы легки в том, что виртуальная машина Erlang не создает поток ОС для каждого созданного процесса. Они создаются, планируются и обрабатываются в виртуальной машине независимо от базовой операционной системы. В результате время создания процесса составляет порядка микросекунд и не зависит от числа одновременно существующих процессов. Сравните это с Java и C #, где для каждого процесса создается базовый поток ОС: вы получите несколько очень конкурентоспособных сравнений, причем Erlang значительно превосходит оба языка.

Из ориентированного на параллелизм программирования в Erlang (pdf) (слайды) (2003):

Мы отмечаем, что время, необходимое для создания процесса Эрланга, составляет от 1 мкс до 2500 процессов; после этого он увеличивается примерно до 3 мкс до 30 000 процессов. Производительность Java и C # показана в верхней части рисунка. Для небольшого числа процессов создание процесса занимает около 300 мкс. Создание более двух тысяч процессов невозможно.

Мы видим, что для 30 000 процессов время отправки сообщения между двумя процессами Erlang составляет около 0,8 мкс. Для C # требуется около 50 мкс на сообщение, до максимального количества процессов (которое составляло около 1800 процессов). Java была еще хуже: на 100 процессов приходилось около 50 мкс на сообщение, после чего она быстро увеличивалась до 10 мс на сообщение, когда было около 1000 Java-процессов.

Мои мысли

Я не совсем понимаю, с технической точки зрения, почему процессы Эрланга намного эффективнее порождают новые процессы и имеют гораздо меньшие объемы памяти на процесс. И ОС, и Erlang VM должны выполнять планирование, переключение контекста и отслеживать значения в регистрах и так далее ...

Просто почему потоки ОС не реализуются так же, как процессы в Erlang? Должны ли они поддерживать что-то большее? И зачем им больше памяти? И почему у них медленнее нерест и общение?

Технически, почему процессы в Erlang более эффективны, чем потоки ОС, когда речь идет о порождении и коммуникации? И почему потоки в ОС не могут быть реализованы и управляться так же эффективно? И почему потоки ОС занимают больше места в памяти, а также медленнее порождают и общение?

Больше чтения

Jonas
источник
1
Прежде чем пытаться понять причину, по которой гипотеза верна, вам необходимо установить , верна ли гипотеза - например, подтверждена ли она доказательствами. У вас есть ссылки на любые как-для подобных сравнений , демонстрирующих , что процесс Erlang на самом деле является более эффективным , чем (скажем) нити Java на восходящем до даты JVM? Или приложение C, использующее процесс OS и поддержку потоков напрямую? (Последнее мне кажется очень, очень маловероятным. Первое только несколько вероятно.) Я имею в виду, что при достаточно ограниченном окружении (точка зрения Франциско) это может быть правдой, но я бы хотел увидеть цифры.
TJ Crowder
1
@Donal: Как и в случае со многими другими абсолютными утверждениями. :-)
TJ Crowder
1
@Jonas: Спасибо, но я дошел до даты (1998-11-02) и версии JVM (1.1.6) и остановился. JVM от Sun значительно улучшилась за последние 11,5 лет (и, по-видимому, и у переводчика Эрланга), особенно в области многопоточности. (Просто чтобы прояснить, я не говорю, что эта гипотеза не верна [и Франциско и Донал указали, почему Эрланд может сделать что-то там); я говорю, что это не следует принимать за чистую монету. без проверки.)
TJ Crowder
1
@Jonas: «... но я думаю, что ты можешь сделать это в Эрланге ...» Это та часть «угадай», чувак. :-) Вы догадываетесь, что переключение процессов Эрланга возрастает за тысячи. Вы догадываетесь, что это лучше, чем потоки Java или OS. Угадай и разработчик программного обеспечения не очень хорошая комбинация. :-) Но я думаю, что сделал свою точку зрения.
TJ Crowder
17
@TJ Crowder: установите erlang и запустите, затем erl +P 1000100 +hms 100напечатайте {_, PIDs} = timer:tc(lists,map,[fun(_)->spawn(fun()->receive stop -> ok end end) end, lists:seq(1,1000000)]).и затем подождите около трех минут, чтобы получить результат. Это так просто. Это занимает 140 мкс на процесс и 1 ГБ оперативной памяти на моем ноутбуке. Но это непосредственно оболочка, лучше скомпилированный код.
Гинек -Пичи- Выходил

Ответы:

113

Есть несколько способствующих факторов:

  1. Erlang процессы не являются процессами ОС. Они реализуются виртуальной машиной Erlang с использованием облегченной модели совместной потоковой обработки (преимущественной на уровне Erlang, но под управлением совместно запланированной среды выполнения). Это означает, что переключать контекст намного дешевле, поскольку они переключаются только в известных контролируемых точках и, следовательно, не должны сохранять все состояние ЦП (нормальное состояние, регистры SSE и FPU, отображение адресного пространства и т. Д.).
  2. В процессах Эрланга используются динамически распределенные стеки, которые начинаются очень маленькими и растут по мере необходимости. Это позволяет порождать многие тысячи - даже миллионы - процессов Erlang, не занимая всю доступную оперативную память.
  3. Erlang имел обыкновение быть однопоточным, что означало, что не было необходимости обеспечивать безопасность потоков между процессами. Теперь он поддерживает SMP, но взаимодействие между процессами Erlang в одном и том же планировщике / ядре все еще очень легкое (есть отдельные очереди выполнения на ядро).
Марсело Кантос
источник
6
К вашему 2-му пункту: и если процесс еще не запущен, нет причин выделять для него стек. Кроме того: Некоторые трюки можно воспроизвести, манипулируя GC процесса, который никогда не собирает память. Но это продвинуто и несколько опасно :)
Я ОТВЕТИЛ ОТЛИЧНЫЕ ОТВЕТЫ
3
К вашему третьему пункту: Erlang применяет неизменные данные, поэтому внедрение SMP не должно влиять на безопасность потоков.
Nilskp
@ nilskp, верно, erlang также является функциональным языком программирования. Так что нет «переменных» данных. Это приводит к поточной безопасности.
liuyang1
6
@nilskp: (RE: вы комментируете пункт 3 ...) Даже если сам язык имеет систему неизменяемых типов, базовая реализация - передача сообщений, планировщик и т. д. - это совсем другая история. Корректная и эффективная поддержка SMP произошла не только одним движением переключателя.
Марсело Кантос
@rvirding: Спасибо за уточняющее приложение. Я взял на себя смелость интегрировать ваши пункты в основную часть моего ответа.
Марсело Кантос
73

После еще одного исследования я нашел презентацию Джо Армстронга.

От Erlang - программное обеспечение для параллельного мира (презентация) (в 13 мин):

[Erlang] - это параллельный язык - я имею в виду, что потоки являются частью языка программирования, а не принадлежат операционной системе. Это действительно то, что не так с языками программирования, такими как Java и C ++. Его потоки не на языке программирования, потоки - это что-то в операционной системе - и они наследуют все проблемы, которые возникают в операционной системе. Одна из проблем - гранулярность системы управления памятью. Управление памятью в операционной системе защищает целые страницы памяти, поэтому наименьший размер потока может быть наименьшим размером страницы. Это на самом деле слишком большой.

Если вы добавите больше памяти на свой компьютер - у вас будет такое же количество битов, которое защищает память, так что степень детализации таблиц страниц возрастет - вы в конечном итоге будете использовать, скажем, 64 КБ для процесса, который, как вы знаете, выполняется в несколько сотен байтов.

Я думаю, что он отвечает, если не все, по крайней мере, на несколько моих вопросов

Jonas
источник
2
Связанный: stackoverflow.com/questions/2267545/…
Джонас
2
Защита памяти в стеках существует не просто так. Разве Erlang не защищает стеки различных контекстов выполнения через MMU процессора? (И просто надеяться на лучшее?) Что если поток использует больше, чем крошечный стек? (Проверяются ли все распределения стека, чтобы увидеть, нужен ли больший стек? Является ли стек подвижным?)
Танатос,
2
@Tanatos: Erlang не позволяет программам обращаться к памяти или работать со стеком. Все выделения должны проходить через управляемую среду выполнения, как в куче, так и в стеке. Другими словами: аппаратная защита бесполезна, потому что она защищает от вещей, которые не могут произойти в любом случае. Этот язык безопасен для указателей, стека, памяти и типов. Процесс не может использовать больше, чем его «крошечный стек», потому что стек увеличивается по мере необходимости. Вы можете думать об этом как о противоположности крошечного: бесконечно большого. (Но лениво выделяется.)
Йорг Миттаг,
4
Вам следует взглянуть на операционную систему Singularity от Microsoft Research. В Singularity весь код, ядро, драйверы устройств, библиотеки и пользовательские программы работают в кольце 0 с полными привилегиями ядра. Весь код, ядро, драйверы устройств, библиотеки и пользовательские программы работают в едином плоском физическом адресном пространстве без какой-либо защиты памяти. Команда обнаружила, что гарантии, предоставляемые языком, намного сильнее, чем гарантии, которые может дать MMU, и в то же время использование MMU обходится им до 30% (!!!) в производительности. Итак, зачем использовать MMU, если ваш язык уже это делает?
Йорг Миттаг,
1
Операционная система OS / 400 работает так же. Для всех программ существует только одно плоское адресное пространство. И большинство языков, используемых в настоящее время, имеют одинаковые свойства безопасности (ECMAScript, Java, C♯, VB.NET, PHP, Perl, Python, Ruby, Clojure, Scala, Kotlin, Groovy, Ceylon, F♯, OCaml, «Объективная» часть «Objective-C», «++» часть «C ++»). Если бы не унаследованный код C и унаследованные функции C ++ и Objective-C, нам бы даже не понадобилась виртуальная память.
Йорг Миттаг,
47

Я реализовал сопрограммы в ассемблере и измерил производительность.

Переключение между сопрограммами, или процессами Эрланга, занимает около 16 инструкций и 20 наносекунд на современном процессоре. Кроме того, вы часто знаете процесс, на который вы переключаетесь (пример: процесс, получающий сообщение в своей очереди, может быть реализован как прямая передача вызова от вызывающего процесса к процессу получения), поэтому планировщик не вступает в игру, делая это операция O (1).

Чтобы переключать потоки ОС, требуется около 500-1000 наносекунд, потому что вы обращаетесь к ядру. Планировщик потоков ОС может выполняться за O (log (n)) или O (log (log (n))), что станет заметно, если у вас десятки тысяч или даже миллионы потоков.

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

Серфер джефф
источник
33

Эрланговские процессы соответствуют (приблизительно) зеленым нитям на других языках; нет никакого принудительного разделения между процессами. (Возможно, существует разделение по языку, но это меньшая защита, несмотря на то, что Эрланг выполняет лучшую работу, чем большинство.) Поскольку они намного легче, их можно использовать гораздо шире.

Потоки ОС, с другой стороны, могут быть просто запланированы на разные ядра ЦП и (в основном) способны поддерживать независимую обработку, связанную с ЦП. Процессы ОС похожи на потоки ОС, но с гораздо более сильным разделением на основе ОС. Цена этих возможностей заключается в том, что потоки ОС и (тем более) процессы стоят дороже.


Еще один способ понять разницу заключается в следующем. Предположим, что вы собираетесь написать реализацию Erlang поверх JVM (не очень сумасшедшее предложение), тогда вы сделаете каждый процесс Erlang объектом с некоторым состоянием. Затем у вас будет пул экземпляров Thread (обычно измеряемый в соответствии с количеством ядер в вашей хост-системе; это настраиваемый параметр в реальных средах исполнения Erlang, кстати), которые запускают процессы Erlang. В свою очередь, это распределит работу, которая должна быть сделана, по реальным доступным системным ресурсам. Это довольно аккуратный способ ведения дел, но полностьюна факте, что каждый отдельный процесс Эрланга не делает очень много. Это нормально, конечно; Erlang структурирован так, чтобы не требовать, чтобы эти отдельные процессы были тяжеловесными, поскольку весь их ансамбль выполняет программу.

Во многих отношениях настоящая проблема заключается в терминологии. То, что Эрланг называет процессами (и которые строго соответствуют одному и тому же понятию в CSP, CCS и особенно π-исчислении), просто не совпадает с тем, что языки с наследием C (включая C ++, Java, C # и многие другие) называют процесс или поток. Есть некоторые сходства (все включают некоторое понятие параллельного выполнения), но определенно нет эквивалентности. Так что будьте осторожны, когда кто-то говорит вам «процесс»; они могут понять, что это означает что-то совершенно другое ...

Donal Fellows
источник
3
Эрланг не подходит близко к Pi Calculus. Исчисление Пи предполагает синхронные события по каналам, которые могут быть связаны с переменными. Такая концепция не подходит для модели Erlang. Попробуйте Join Calculus, Erlang ближе к этому, хотя он все еще должен иметь возможность изначально присоединяться к некоторым сообщениям и еще много чего. Был реализован тезис (и проект) под названием JErlang, посвященный его реализации.
Я ДАЮ ТЕКРИЛЬНЫЙ СОВЕТ
Все зависит от того, что именно вы считаете пи-исчислением (и вы можете моделировать асинхронные каналы с синхронными каналами плюс буферные процессы).
Donal Fellows
Вы просто говорите, что процессы Erlang являются легковесными, но вы не объясняете, почему они занимают меньше места (имеют малый вес) и почему они имеют лучшую производительность, чем потоки ОС.
Джонас
1
@Jonas: для некоторых типов задач (особенно для задач, требующих большого объема вычислений) потоки ОС работают лучше. Имейте в виду, это не те задачи, для которых используется Erlang; Эрланг ориентирован на большое количество простых задач общения. Одним из преимуществ этого является то, что в случае группы задач, которые выполняют часть работы и ожидают результата, все это можно выполнить в одном потоке ОС на одном процессоре, что более эффективно, чем имея переключатели контекста.
Donal Fellows
Теоретически, вы можете сделать поток ОС слишком дешевым, используя очень маленький стек и тщательно контролируя количество других выделенных для потока ресурсов, но на практике это довольно проблематично. (Прогнозирование требований к стеку - это не что иное, как черное искусство.) Таким образом, потоки ОС специально разработаны так, чтобы быть оптимальными в случае, когда их меньше (порядка числа ядер ЦП) и когда они работают более значимо. суммы обработки каждый.
Donal Fellows
3

Я думаю, что Джонасу нужны были цифры для сравнения потоков ОС с процессами Эрланга. Джо Армстронг, автор Programming Erlang, некоторое время назад проверил масштабируемость процессов Erlang в потоках ОС. Он написал простой веб-сервер на Erlang и протестировал его на многопоточном Apache (поскольку Apache использует потоки ОС). Есть старый сайт с данными за 1998 год. Мне удалось найти его только один раз. Поэтому я не могу предоставить ссылку. Но информация там. Суть исследования показала, что Apache максимально справился с процессами под 8K, в то время как его рукописный сервер Erlang обрабатывал процессы с 10K +.

Jurnell
источник
5
Я думаю, что вы говорите об этом: sics.se/~joe/apachevsyaws.html Но я спросил, как erlang делает потоки настолько эффективными по сравнению с потоками kerlenl.
Джонас
@Jonas ссылка мертва. Последний снимок здесь
alvaro g
1
В статье говорится: «Apache умирает около 4000 параллельных сессий. Yaws по-прежнему работает с более чем 80 000 параллельных соединений».
Натан Лонг
см. полную статью на citeseerx.ist.psu.edu/viewdoc/… Действительно, оказалось невозможным сломать сервер Erlang, используя 16 атакующих машин - хотя было легко остановить сервер Apache.
Бернхард
1

Поскольку интерпретатору Erlang нужно беспокоиться только о себе, в ОС есть много других проблем.

Франсиско Сото
источник
0

одна из причин заключается в том, что процесс erlang создается не в ОС, а в evm (виртуальная машина erlang), поэтому его стоимость меньше.

ratzily
источник