Является ли F # действительно быстрее, чем Эрланг в процессах нереста и убийства?
Обновлено: этот вопрос содержит ошибку, которая делает этот тест бессмысленным. Я попытаюсь использовать более эффективный тест, сравнивающий функциональные возможности F # и Erlang basic concurrency и узнайте о результатах в другом вопросе.
Я пытаюсь понять характеристики производительности Erlang и F #. Я нахожу модель Erlang concurrency очень привлекательной, но я склонен использовать F # для соображений совместимости. Несмотря на то, что F # не предлагает ничего подобного примитивам Erlang concurrency - из того, что я могу сказать, async и MailboxProcessor охватывают только небольшую часть того, что делает Erlang, - я пытался понять, что возможно в F # производительность.
В книге Джо Эрнланга "Программирование Эрланг" он подчеркивает, что в Эрланге процессы очень дешевы. Он использует (примерно) следующий код, чтобы продемонстрировать этот факт:
-module(processes).
-export([max/1]).
%% max(N)
%% Create N processes then destroy them
%% See how much time this takes
max(N) ->
statistics(runtime),
statistics(wall_clock),
L = for(1, N, fun() -> spawn(fun() -> wait() end) end),
{_, Time1} = statistics(runtime),
{_, Time2} = statistics(wall_clock),
lists:foreach(fun(Pid) -> Pid ! die end, L),
U1 = Time1 * 1000 / N,
U2 = Time2 * 1000 / N,
io:format("Process spawn time=~p (~p) microseconds~n",
[U1, U2]).
wait() ->
receive
die -> void
end.
for(N, N, F) -> [F()];
for(I, N, F) -> [F()|for(I+1, N, F)].
На моем Macbook Pro нерестится и убивает 100 тысяч процессов (processes:max(100000)
) занимает около 8 микросекунд на каждый процесс. Я могу немного увеличить количество процессов, но миллион, похоже, довольно быстро нарушает работу.
Зная очень мало F #, я попытался реализовать этот пример, используя async и MailBoxProcessor. Моя попытка, которая может быть неправильной, заключается в следующем:
#r "System.dll"
open System.Diagnostics
type waitMsg =
| Die
let wait =
MailboxProcessor.Start(fun inbox ->
let rec loop =
async { let! msg = inbox.Receive()
match msg with
| Die -> return() }
loop)
let max N =
printfn "Started!"
let stopwatch = new Stopwatch()
stopwatch.Start()
let actors = [for i in 1 .. N do yield wait]
for actor in actors do
actor.Post(Die)
stopwatch.Stop()
printfn "Process spawn time=%f microseconds." (stopwatch.Elapsed.TotalMilliseconds * 1000.0 / float(N))
printfn "Done."
Используя F # на Mono, запуск и убийство 100 000 участников/процессоров занимает менее 2 микросекунд на процесс, примерно в 4 раза быстрее, чем у Erlang. Что еще более важно, возможно, это то, что я могу масштабировать до миллионов процессов без каких-либо очевидных проблем. Начиная 1 или 2 миллиона процессов все еще занимает около 2 микросекунд на процесс. Начиная с 20 миллионов процессоров по-прежнему возможно, но замедляется примерно до 6 микросекунд на каждый процесс.
Я еще не нашел времени, чтобы полностью понять, как F # реализует async и MailBoxProcessor, но эти результаты обнадеживают. Есть ли что-то, что я делаю ужасно неправильно?
Если нет, есть ли место, где Erlang, скорее всего, превзойдет F #? Есть ли причина, по которой примитивы Erlang concurrency не могут быть перенесены в F # через библиотеку?
EDIT: приведенные выше цифры ошибочны, из-за ошибки, которую указал Брайан. Я буду обновлять весь вопрос, когда я его исправлю.
Ответы
Ответ 1
В исходном коде вы только запустили один почтовый сервер. Сделайте wait()
функцию и вызовите ее с каждым yield
. Кроме того, вы не дожидаетесь их появления или получения сообщений, которые, как я думаю, недействительны для информации о времени; см. мой код ниже.
Тем не менее, у меня есть некоторый успех; на моей коробке я могу сделать 100 000 примерно по 25 штук каждый. После слишком многого, я думаю, что, возможно, вы начнете бороться с распределителем /GC столько, сколько угодно, но я тоже смог сделать миллион (примерно по 27US каждый, но на данный момент использовал как 1.5G памяти).
В основном каждый "приостановленный асинхронный" (который является состоянием, когда почтовый ящик ожидает строку, например
let! msg = inbox.Receive()
) принимает только некоторое количество байтов при блокировке. Вот почему вы можете иметь путь, путь, путь более асинхронный, чем потоки; поток обычно занимает мегабайт памяти или больше.
Хорошо, вот код, который я использую. Вы можете использовать небольшое число, например 10, и --define DEBUG, чтобы гарантировать, что семантика программы является желаемой (выходы printf могут чередоваться, но вы получите идею).
open System.Diagnostics
let MAX = 100000
type waitMsg =
| Die
let mutable countDown = MAX
let mre = new System.Threading.ManualResetEvent(false)
let wait(i) =
MailboxProcessor.Start(fun inbox ->
let rec loop =
async {
#if DEBUG
printfn "I am mbox #%d" i
#endif
if System.Threading.Interlocked.Decrement(&countDown) = 0 then
mre.Set() |> ignore
let! msg = inbox.Receive()
match msg with
| Die ->
#if DEBUG
printfn "mbox #%d died" i
#endif
if System.Threading.Interlocked.Decrement(&countDown) = 0 then
mre.Set() |> ignore
return() }
loop)
let max N =
printfn "Started!"
let stopwatch = new Stopwatch()
stopwatch.Start()
let actors = [for i in 1 .. N do yield wait(i)]
mre.WaitOne() |> ignore // ensure they have all spun up
mre.Reset() |> ignore
countDown <- MAX
for actor in actors do
actor.Post(Die)
mre.WaitOne() |> ignore // ensure they have all got the message
stopwatch.Stop()
printfn "Process spawn time=%f microseconds." (stopwatch.Elapsed.TotalMilliseconds * 1000.0 / float(N))
printfn "Done."
max MAX
Все это сказало, я не знаю Erlang, и я не думал глубоко о том, есть ли способ обрезать F # больше (хотя он довольно идиоматический как есть).
Ответ 2
Erlang VM не использует потоки ОС или процесс переключается на новый процесс Erlang. Он просто подсчитывает вызовы функций в ваш код/процесс и переходит к другому процессу VM после некоторого (в тот же процесс ОС и тот же поток ОС).
CLR использует механику на основе процесса ОС и потоков, поэтому F # имеет гораздо более высокие накладные расходы для каждого переключателя контекста.
Итак, ответ на ваш вопрос: "Нет, Эрланг намного быстрее, чем нерестится и убивает процессы".
P.S. Вы можете найти результаты этого практического конкурса.