在产卵和查杀过程中,F#真的比Erlang快吗?
更新:这个问题包含一个错误,使基准毫无意义。我将尝试一个比较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#,开始和杀害100000名演员/处理器下每个进程2微秒花费,比二郎神快约4倍。更重要的是,也许,我可以扩展到数百万个进程而没有任何明显的问题。每个进程启动1或2百万个进程仍需要大约2微秒。启动2000万个处理器仍然可行,但每个进程的速度减慢到6微秒左右。
我还没有花时间来完全理解F#如何实现async和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
这一切说,我不知道二郎,我还没有深入想过是否有修剪下来的F#任何更多的(虽然这是很地道的不变化)的方法。
Erlang的虚拟机不使用操作系统线程或进程切换到新的Erlang进程。它只是将函数调用计入您的代码/进程中,并在一些(在相同的OS进程和相同的OS线程中)之后跳转到其他VM的进程。
CLR使用基于OS进程和线程的机制,所以F#对每个上下文切换都有更高的开销成本。
所以回答你的问题是“不,Erlang比产卵和杀死过程要快得多”。
P.S.你可以找到results of that practical contest有趣。
这就是我的想法,这就是为什么我发现我的结果令人困惑。但他们错了。然而,这是一种内在的限制,还是目前设计的方式?我没有完全看它,但单线程异步描述在这里:http://cs.hubfs.net/blogs/hell_is_other_languages/archive/2008/08/03/6506.aspx – Tristan 2010-02-07 07:31:20
这是用户空间线程很好的例子。您可以看到F#的源代码,他们的异步使用CLR ThreadPool。 – ssp 2010-02-07 08:09:56
F#asyncs是用户空间线程。他们没有与OS线程相关的开销。他们可以使用线程池来利用多个内核,但从来没有比硬件本身支持更多的线程,并且常见类型的上下文切换保留在单个OS线程内。使用单一(非超线程)核心,它们纯粹是用户空间线程,如Erlang。 – RD1 2010-04-28 15:39:59
+1对于真正高级的问题。 – gahooa 2010-02-06 22:50:37
+1也很有趣,不仅仅是先进的。 – gradbot 2010-02-06 23:12:26
您是否使用Erlang的“erl + native”选项? (请参阅http://*.com/questions/2207451/erlang-compilation-mixed-of-hipe-object-code-and-opcode) – jldupont 2010-02-07 13:38:28