在产卵和查杀过程中,F#真的比Erlang快吗?

更新:这个问题包含一个错误,使基准没有意义。 我将尝试一个比较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上,产生并杀死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名actor / processor的时间都在2微秒以内,比Erlang快大约4倍。 更重要的是,也许,我可以扩展到数百万个进程而没有任何明显的问题。 每个进程启动1或2百万个进程仍需要大约2微秒。 启动2000万个处理器仍然可行,但每个进程的速度减慢到6微秒左右。

我还没有花时间来完全理解F#如何实现异步和MailBoxProcessor,但这些结果令人鼓舞。 我有什么可怕的错误吗?

如果没有,Erlang有可能会超越F#吗? 是否有任何理由Erlang的并发原语不能通过库带到F#中?

编辑:上述数字是错误的,由于错误布赖恩指出。 当我修复它时,我会更新整个问题。


在您的原始代码中,您只启动了一个MailboxProcessor。 使wait()成为函数,并在每个yield调用它。 此外,您并未等待他们旋转或接收信息,我认为这会使时间信息无效; 看到我的代码如下。

这就是说,我取得了一些成功; 在我的盒子上,我可以在大约25us的时候做10万次。 再过多之后,我认为你可能会像任何事情一样对分配器/ 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#的数量(尽管它现在是非常习惯的)。


Erlang的虚拟机不使用操作系统线程或进程切换到新的Erlang进程。 它只是将函数调用计入您的代码/进程中,并在一些(在相同的OS进程和相同的OS线程中)之后跳转到其他VM的进程。

CLR使用基于OS进程和线程的机制,因此F#对每个上下文切换的开销成本要高得多。

所以回答你的问题是“不,Erlang比产卵和杀死过程快得多”。

PS你可以找到有趣的实际比赛的结果。

链接地址: http://www.djcxy.com/p/43191.html

上一篇: Is F# really faster than Erlang at spawning and killing processes?

下一篇: Difference in GHC versions