在生成和杀死进程时,F#真的比Erlang快吗? [英] Is F# really faster than Erlang at spawning and killing processes?
问题描述
更新:此问题包含一个错误,使基准测试无意义。我将尝试比较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屋!