code

F #은 프로세스 생성 및 종료에서 Erlang보다 정말 빠릅니까?

codestyles 2020. 12. 2. 21:21
반응형

F #은 프로세스 생성 및 종료에서 Erlang보다 정말 빠릅니까?


업데이트 됨 :이 질문에는 벤치 마크를 무의미하게 만드는 오류가 포함되어 있습니다. F #과 Erlang의 기본 동시성 기능을 비교하는 더 나은 벤치 마크를 시도하고 다른 질문에서 결과에 대해 문의하겠습니다.

Erlang과 F #의 성능 특성을 이해하려고 노력하고 있습니다. Erlang의 동시성 모델이 매우 매력적이라고 ​​생각하지만 상호 운용성을 위해 F #을 사용하는 경향이 있습니다. 즉시 사용할 수있는 F #은 Erlang의 동시성 프리미티브와 같은 것을 제공하지 않지만 비동기식으로 말할 수 있고 MailboxProcessor는 Erlang이 잘하는 작업의 일부만 다루고 있습니다 .F # 성능에서 가능한 것이 무엇인지 이해하려고 노력했습니다. 슬기로운.

Joe Armstrong의 Programming 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에서 10 만 개의 프로세스를 생성하고 종료하는 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."

Mono에서 F #을 사용하면 100,000 명의 액터 / 프로세서를 시작하고 죽이는 데 프로세스 당 2 마이크로 초 미만이 걸리며 이는 Erlang보다 약 4 배 더 빠릅니다. 더 중요한 것은 아마도 명백한 문제없이 수백만 개의 프로세스로 확장 할 수 있다는 것입니다. 1 백만 또는 2 백만 개의 프로세스를 시작하는 데는 여전히 프로세스 당 약 2 마이크로 초가 걸립니다. 2 천만 개의 프로세서를 시작하는 것은 여전히 ​​가능하지만 프로세스 당 약 6 마이크로 초로 느려집니다.

F #이 비동기 및 MailBoxProcessor를 구현하는 방법을 완전히 이해하는 데 아직 시간이 걸리지 않았지만 이러한 결과는 고무적입니다. 내가 끔찍하게 잘못하고있는 것이 있습니까?

그렇지 않다면 Erlang이 F #을 능가 할 가능성이있는 곳이 있습니까? Erlang의 동시성 프리미티브를 라이브러리를 통해 F #으로 가져올 수없는 이유가 있습니까?

EDIT: The above numbers are wrong, due to the error Brian pointed out. I will update the entire question when I fix it.


In your original code, you only started one MailboxProcessor. Make wait() a function, and call it with each yield. Also you are not waiting for them to spin up or receive the messages, which I think invalidates the timing info; see my code below.

That said, I have some success; on my box I can do 100,000 at about 25us each. After too much more, I think possibly you start fighting the allocator/GC as much as anything, but I was able to do a million too (at about 27us each, but at this point was using like 1.5G of memory).

Basically each 'suspended async' (which is the state when a mailbox is waiting on a line like

let! msg = inbox.Receive()

) only takes some number of bytes while it's blocked. That's why you can have way, way, way more asyncs than threads; a thread typically takes like a megabyte of memory or more.

Ok, here's the code I'm using. You can use a small number like 10, and --define DEBUG to ensure the program semantics are what is desired (printf outputs may be interleaved, but you'll get the idea).

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

All this said, I don't know Erlang, and I have not thought deeply about whether there's a way to trim down the F# any more (though it's pretty idiomatic as-is).


Erlang's VM doesn't uses OS threads or process to switch to new Erlang process. It's VM simply counts function calls into your code/process and jumps to other VM's process after some (into same OS process and same OS thread).

CLR uses mechanics based on OS process and threads, so F# has much higher overhead cost for each context switch.

So answer to your question is "No, Erlang is much faster than spawning and killing processes".

P.S. You can find results of that practical contest interesting.

참고URL : https://stackoverflow.com/questions/2214954/is-f-really-faster-than-erlang-at-spawning-and-killing-processes

반응형