Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Concurrency F#在产卵和杀死过程中真的比Erlang快吗?_Concurrency_F#_Erlang_Actor - Fatal编程技术网

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

Concurrency F#在产卵和杀死过程中真的比Erlang快吗?,concurrency,f#,erlang,actor,Concurrency,F#,Erlang,Actor,更新:此问题包含一个错误,使基准变得毫无意义。我将尝试一个更好的基准测试,比较F#和Erlang的基本并发功能,并在另一个问题中询问结果 我试图了解Erlang和F#的性能特征。我发现Erlang的并发模型非常吸引人,但出于互操作性的原因,我倾向于使用F。虽然开箱即用的F#没有提供任何类似于Erlang的并发原语的东西——据我所知,async和MailboxProcessor只涵盖了Erlang所擅长的一小部分——但我一直在努力理解F#在性能方面可能实现的功能 在Joe Armstrong的编程

更新:此问题包含一个错误,使基准变得毫无意义。我将尝试一个更好的基准测试,比较F#和Erlang的基本并发功能,并在另一个问题中询问结果

我试图了解Erlang和F#的性能特征。我发现Erlang的并发模型非常吸引人,但出于互操作性的原因,我倾向于使用F。虽然开箱即用的F#没有提供任何类似于Erlang的并发原语的东西——据我所知,async和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上,生成和杀死100000个进程(
进程: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."
在Mono上使用F#,启动和杀死100000个参与者/处理器每个进程不到2微秒,大约比Erlang快4倍。也许更重要的是,我可以扩展到数百万个流程,而不会出现任何明显的问题。启动100万或200万个进程仍然需要每个进程大约2微秒。启动2000万个处理器仍然可行,但每个进程的速度会降低到6微秒左右

我还没有花时间完全理解F#如何实现异步和MailBoxProcessor,但这些结果令人鼓舞。是不是我做错了什么

如果不是的话,是否有一些地方Erlang可能会跑赢F#?有什么原因不能通过库将Erlang的并发原语带到F#吗


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

在您的原始代码中,您只启动了一个MailboxProcessor。使
wait()
成为一个函数,并使用每个
yield
调用它。你也不是在等待他们启动或接收信息,我认为这会使计时信息失效;请参阅下面的代码

也就是说,我取得了一些成功;在我的包厢里,我可以做10万件,每人大约25美元。在经历了太多之后,我想你可能会像其他任何事情一样开始与分配器/GC作斗争,但我也能做到一百万(每个大约27个,但在这一点上使用了大约1.5G的内存)

基本上每个“挂起异步”(这是邮箱在线等待时的状态,如

let! msg = inbox.Receive()
)当它被阻止时只需要一些字节数。这就是为什么你可以有比线程更多的异步方式;一个线程通常占用一兆字节或更多的内存

好的,这是我使用的代码。您可以使用一个小数字,如10和--define DEBUG,以确保程序语义符合要求(printf输出可能是交错的,但您会明白的)

开放式系统诊断
设最大值为100000
类型waitMsg=
|死
设可变倒计时=MAX
设mre=新系统.Threading.ManualResetEvent(错误)
让我们等待(我)
MailboxProcessor.Start(有趣的收件箱->
让rec循环=
异步{
#如果调试
打印fn“我是mbox#%d”I
#恩迪夫
如果System.Threading.Interlocated.Decreation(&倒计时)=0,则
mre.Set()|>忽略
let!msg=inbox.Receive()
配味精
|模具->
#如果调试
打印fn“mbox#%d死亡”i
#恩迪夫
如果System.Threading.Interlocated.Decreation(&倒计时)=0,则
mre.Set()|>忽略
return()}
循环)
设max N=
printfn“开始!”
让秒表=新秒表()
秒表开始
让actors=[对于1..N中的i,不屈服等待(i)]
mre.WaitOne()|>忽略//确保它们都已旋转起来
mre.Reset()|>忽略
倒计时忽略//确保他们都收到了信息
秒表
printfn“进程生成时间=%f微秒。”(stopwatch.appeased.totalmicrosides*1000.0/浮点(N))
printfn“完成”
马克斯马克斯

综上所述,我不了解Erlang,我也没有深入思考是否有办法再减少F#(尽管它已经非常习惯了)。

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

CLR使用基于操作系统进程和线程的机制,因此F#对于每个上下文切换有更高的开销成本

所以,您的问题的答案是“不,Erlang比繁殖和杀死过程快得多”


另外,你会发现很有趣。

+1表示真正高级的问题。+1表示也很有趣,而不仅仅是高级的问题。你对Erlang使用过“erl+native”选项吗?(请参阅)Erlang目前的速度比其在快速喷射过程中的速度慢。proc_tab数组中有一个锁会成为瓶颈,因此将进程预加载到池中会更快。但是,Erlang在生成进程方面相当快。@psyeugenic:如果“进程生成”成本是一个问题,那么无论如何都应该考虑使用“进程池”。“调度开销”和“调度公平性”也应该被考虑,因为整个演习在国际海事组织中是有意义的。我怀疑我可能犯了类似这样的错误。为了确认,我应该用wait()替换这两个wait?我得到很多
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