Frage

Aktualisiert: Diese Frage enthält einen Fehler, der die Benchmark bedeutungslos macht. Ich werde eine bessere Benchmark-Vergleich F # und Erlang Grund Gleichzeitigkeit Funktionalität versuchen und über die Ergebnisse in einer anderen Frage erkundigen.

Ich versuche, die Leistungseigenschaften von Erlang und F # verstehen. Ich finde Erlang Concurrency-Modell sehr ansprechend, aber ich geneigt Fis aus Gründen der Interoperabilität zu verwenden. Während aus der Box F # bietet nichts wie Primitive Erlang Gleichzeitigkeit - von dem, was ich sagen kann, async und MailboxProcessor deckt nur einen kleinen Teil von dem, was Erlang tut gut - ich habe zu verstehen versucht, was in F # Leistung möglich ist, weise.

In Joe Armstrong Programming Erlang Buch, er macht den Punkt, dass Prozesse in Erlang sehr billig sind. Er nutzt den (grob) den folgenden Code ein, diese Tatsache zu demonstrieren:

-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)].

Auf meinem MacBook Pro, Laich- und tötet 100.000 Prozesse (processes:max(100000)) dauert etwa 8 Mikrosekunden pro Prozesse. Ich kann die Anzahl der Prozesse ein bisschen weiter erhöhen, aber eine Million scheint die Dinge ziemlich konsequent zu brechen.

sehr wenig F # Wissen, habe ich versucht, diesem Beispiel zu implementieren async und MailBoxProcessor verwenden. Mein Versuch, die falsch sein kann, ist wie folgt:

#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."

Mit F # auf Mono ab und tötet 100.000 Schauspieler / Prozessoren nehmen unter 2 Mikrosekunden pro Prozess, etwa 4-mal schneller als Erlang. Noch wichtiger ist vielleicht, dass ich ohne offensichtliche Probleme für Millionen von Prozessen skalieren. Ab 1 oder 2 Millionen Prozesse dauert noch etwa 2 Mikrosekunden pro Prozess. Ab 20 Millionen Prozessoren ist immer noch möglich, aber verlangsamt bis etwa 6 Mikrosekunden pro Prozess.

Ich habe noch nicht die Zeit genommen, um vollständig zu verstehen, wie F # async und MailBoxProcessor implementiert, aber diese Ergebnisse sind ermutigend. Gibt es etwas, das ich schrecklich falsch?

Wenn nicht, gibt es einen Ort Erlang wird wahrscheinlich Fis übertreffen? Gibt es einen Grund Primitiven Erlang Gleichzeitigkeit kann nicht auf F # über eine Bibliothek gebracht werden?

EDIT: Die oben genannten Zahlen sind falsch, aufgrund des Fehlers Brian hingewiesen. Ich werde die ganze Frage aktualisieren, wenn ich es beheben.

War es hilfreich?

Lösung

In Ihrem ursprünglichen Code, Sie begann nur einen MailboxProcessor. Machen wait() eine Funktion, und rufen Sie es mit jedem yield. Auch sind Sie nicht auf sie warten, die Nachrichten spinnen oder empfangen, die ich denke, die Timing-Informationen ungültig; siehe meinen Code unten.

Das heißt, ich habe einen gewissen Erfolg; auf meiner Box kann ich 100.000 auf etwa 25US tun jeder. Nach zu viel mehr, ich glaube, vielleicht starten Sie das Allocator / GC so viel wie etwas zu kämpfen, aber ich war in der Lage eine Million zu tun (bei 27us über jeden, aber an diesem Punkt wurde wie 1.5G Speicher verwendet wird).

Grundsätzlich jeder ‚suspendiert async‘ (was der Zustand ist, wenn ein Postfach auf einer Linie wartet wie

let! msg = inbox.Receive()

) hat nur eine bestimmte Anzahl von Bytes, während es gesperrt ist. Deshalb sollten Sie Art und Weise haben, viel, viel mehr als asyncs Fäden; ein Thread erfolgt typischerweise wie ein Megabyte Speicher oder mehr.

Ok, hier ist der Code, den ich verwenden. Sie können eine kleine Zahl wie 10 verwenden, und --define DEBUG das Programm Semantik ist, um sicherzustellen, was gewünscht wird (printf Ausgänge verschachtelt sein können, aber Sie bekommen die Idee).

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

Alle sagte dies, ich weiß nicht, Erlang, und ich habe nicht tief darüber nachgedacht, ob eine Art und Weise gibt es die F # mehr zu trimmen (obwohl es ziemlich idiomatische ist, wie sie ist).

Andere Tipps

Erlang VM nicht verwendet nicht OS-Threads oder Prozess auf neuen Erlang Prozess zu wechseln. Es ist VM zählt einfach Funktionsaufrufe in Ihren Code / Prozess und springt auf andere VMs Prozess nach einigen (in gleichen OS-Prozess und gleichen OS-Thread).

CLR verwendet Mechanik basiert auf OS-Prozess und Threads, so F # viel höhere Gemeinkosten für jeden Kontextschalter hat.

So Antwort auf Ihre Frage ist: „Nein, Erlang ist viel schneller als Laich- und töten Prozesse“.

P. S. Sie können finden Ergebnisse dieser praktischen Wettbewerb interessant.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top