Обновленный: Этот вопрос содержит ошибку, которая делает сравнительный тест бессмысленным. Я буду делать попытку лучшего сравнительного теста, сравнивающего основную функциональность параллелизма F# и Erlang, и справляться о результатах в другом вопросе.
Я пробую, действительно понимают рабочие характеристики Erlang и F#. Я нахожу модель параллелизма Erlang очень привлекательной, но склонен использовать F# по причинам совместимости. В то время как из поля F# не предлагает ничего как примитивы параллелизма Erlang - от того, что я могу сказать асинхронный, и MailboxProcessor только покрывают небольшую часть того, что преуспевает Erlang - я пытался понять то, что возможно в мудрой производительности F#.
В Программировании Joe Armstrong книга Erlang он высказывает мнение, которое процессы являются очень дешевыми в Erlang. Он использует (примерно) следующий код для демонстрации этого факта:
-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#, я пытался реализовать этот пример, использующий асинхронный и 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# на Моно, стартовых и уничтожающих 100 000 агентов/процессоров занимает менее чем 2 микросекунды для каждого процесса, примерно в 4 раза быстрее, чем Erlang. Что еще более важно, возможно, то, что я могу увеличиться к миллионам процессов без любых очевидных проблем. Запуск 1 или 2 миллионов процессов все еще занимает приблизительно 2 микросекунды для каждого процесса. Запуск 20 миллионов процессоров все еще выполним, но замедляется приблизительно к 6 микросекундам для каждого процесса.
Я еще не не торопился, чтобы полностью понять, как F# реализует асинхронный и MailBoxProcessor, но эти результаты воодушевляют. Есть ли что-то, что я делаю ужасно неправильно?
В противном случае есть ли некоторое место, Erlang, вероятно, превзойдет F# по характеристикам? Есть ли какая-либо причина, которая примитивы параллелизма Erlang не могут быть принесены к F# через библиотеку?
Править: Вышеупомянутые числа являются неправильными, из-за ошибки, на которую указал Brian. Я обновлю весь вопрос, когда я зафиксирую его.
В исходном коде вы запустили только один MailboxProcessor. Сделайте wait ()
функцией и вызывайте ее с каждым yield
. Кроме того, вы не ждете, когда они раскрутятся или получат сообщения, что, как я думаю, делает недействительной информацию о времени; см. мой код ниже.
Тем не менее, я добился некоторого успеха; на моем ящике я могу сделать 100000 примерно по 25 мкс каждый. После слишком многого, я думаю, что, возможно, вы начнете бороться с распределителем / сборщиком мусора как угодно, но я тоже смог сделать миллион (примерно по 27 мкс каждый, но на тот момент использовал примерно 1,5 ГБ памяти).
По сути, каждый «приостановленный асинхронный режим» (состояние, в котором почтовый ящик ожидает в строке вроде
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 # еще (хотя это довольно идиоматично как есть).
Виртуальная машина Erlang не использует потоки ОС или процесс для переключения на новый процесс Erlang. Это виртуальная машина просто подсчитывает вызовы функций в вашем коде / процессе и после некоторых переходит к другому процессу виртуальной машины (в тот же процесс ОС и тот же поток ОС).
CLR использует механизмы, основанные на процессах и потоках ОС, поэтому F # требует гораздо более высоких накладных расходов для каждого переключения контекста.
Итак, ответ на ваш вопрос: «Нет, Erlang намного быстрее, чем процессы порождения и уничтожения».
P.S. Интересны итоги практического конкурса .