在生成和杀死进程时,F#真的比Erlang快吗? [英] Is F# really faster than Erlang at spawning and killing processes?

查看:657
本文介绍了在生成和杀死进程时,F#真的比Erlang快吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我试图了解性能特征Erlang和F#。我发现Erlang的并发模型非常有吸引力,但我倾向于使用F#的互操作性原因。虽然开箱即用F#不提供像Erlang的并发原语 - 从我可以告诉异步和MailboxProcessor只覆盖了一小部分Erlang做得很好 - 我一直在试图了解什么是可能的F#性能



在Joe Armstrong的编程Erlang书中,他指出在Erlang中进程非常便宜。他使用(粗略地)下面的代码来演示这个事实:

  -module(processes)。 
-export([max / 1])。

%% max(N)
%%创建N个进程然后销毁它们
%%查看这需要多少时间

max(N) - >
statistics(runtime),
statistics(wall_clock),
L = for(1,N,fun() - & ),
{_,Time1} = statistics(runtime),
{_,Time2} = statistics(wall_clock),
list:foreach(fun(Pid) - & end),
U1 = Time1 * 1000 / N,
U2 = Time2 * 1000 / N,
io:format(Process spawn time =〜p 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万个进程(进程:max (100000))每个进程大约需要8微秒。我可以提高进程的数量进一步,但一百万似乎打破事情一贯。



知道很少的F#,我试图实现这个例子使用异步和MailBoxProcessor。我的尝试,可能是错误的,如下:

  #rSystem.dll
打开系统。诊断

type waitMsg =
| die

let wait =
MailboxProcessor.Start(fun inbox - >
let rec loop =
async {let!msg = inbox.Receive $ b match msg with
| Die - > return()}
loop)

let max N =
printfnStarted!
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进程产生时间=%f微秒。 (stopwatch.Elapsed.TotalMilliseconds * 1000.0 / float(N))
printfnDone。

在Mono上使用F#,启动和杀死100,000个actors / processor每个进程需要2微秒以下,大约4比Erlang快。更重要的是,也许,我可以扩展到数百万的进程没有任何明显的问题。开始1或2百万个进程每个进程仍需要大约2微秒。启动2000万个处理器仍然可行,但每个进程减少到大约6微秒。



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



如果没有,是否有一些地方Erlang可能会超过F#?是否有任何原因Erlang的并发原语不能通过库到F#



编辑:上面的数字是错误的,由于错误Brian指出。

解决方案

在您的原始代码中,您只启动了一个MailboxProcessor。 Make wait()一个函数,并用每个 yield 调用它。此外,你不是在等待他们旋转或接收消息,我认为无效的时间信息;请参阅下面的代码。



也就是说,我取得了一些成功;在我的盒子上,我可以做到100,000在每个约25us。过了太多以后,我想可能你开始与分配器/ GC一样打击,但我能够做一百万(每个大约27us,但在这一点是使用像1.5G的内存)。

基本上每个暂停的异步(这是邮箱正在等待一行的状态,如

  let!msg = inbox.Receive()

的字节。这就是为什么你可以有方式,方式,方式更多的异步线程;一个线程通常需要像一兆字节的内存或更多。



好吧,这里是我使用的代码。你可以使用一个小数字,如10,和 - 定义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。开始(fun inbox - >
let rec loop =
async {
#if DEBUG
printfn我是mbox#%di
#endif
if System.Threading.Interlocked.Decrement(& countDown)= 0 then
mre.Set()|> ignore
let!msg = inbox.Receive()
match msg with
| Die - >
#if DEBUG
printfnmbox#%d diedi
#endif
if System.Threading.Interlocked.Decrement(& ; countDown)= 0 then
mre.Set()|> ignore
return()}
loop)

let max N =
printfn 开始!
let stopwatch = new Stopwatch()
stopwatch.Start()
let actors = [for i in 1 .. N do yield wait(i)]
mre.WaitOne )|>忽略//确保它们都启动了
mre.Reset()|>忽略
countDown< - MAX
对于actor in actors do
actor.Post(Die)
mre.WaitOne()|>忽略//确保它们都有消息
stopwatch.Stop()
printfn进程产生时间=%f微秒。 (stopwatch.Elapsed.TotalMilliseconds * 1000.0 / float(N))
printfnDone。

max MAX

这一切都说,我不知道Erlang,我并没有深思过是否有一种方法来修剪F#(虽然它是很惯用的)。


Updated: This question contains an error which makes the benchmark meaningless. I will attempt a better benchmark comparing F# and Erlang's basic concurrency functionality and inquire about the results in another question.

I am trying do understand the performance characteristics of Erlang and F#. I find Erlang's concurrency model very appealing but am inclined to use F# for interoperability reasons. While out of the box F# doesn't offer anything like Erlang's concurrency primitives -- from what I can tell async and MailboxProcessor only cover a small portion of what Erlang does well -- I've been trying to understand what is possible in F# performance wise.

In Joe Armstrong's Programming Erlang book, he makes the point that processes are very cheap in Erlang. He uses the (roughly) the following code to demonstrate this fact:

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

On my Macbook Pro, spawning and killing 100 thousand processes (processes:max(100000)) takes about 8 microseconds per processes. I can raise the number of processes a bit further, but a million seems to break things pretty consistently.

Knowing very little F#, I tried to implement this example using async and MailBoxProcessor. My attempt, which may be wrong, is as follows:

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

Using F# on Mono, starting and killing 100,000 actors/processors takes under 2 microseconds per process, roughly 4 times faster than Erlang. More importantly, perhaps, is that I can scale up to millions of processes without any apparent problems. Starting 1 or 2 million processes still takes about 2 microseconds per process. Starting 20 million processors is still feasible, but slows to about 6 microseconds per process.

I have not yet taken the time to fully understand how F# implements async and MailBoxProcessor, but these results are encouraging. Is there something I'm doing horribly wrong?

If not, is there some place Erlang will likely outperform F#? Is there any reason Erlang's concurrency primitives can't be brought to F# through a library?

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

这篇关于在生成和杀死进程时,F#真的比Erlang快吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆