更新:此问题包含一个错误,导致基准测试毫无意义。我将尝试一个更好的基准来比较 F# 和 Erlang 的基本并发功能,并在另一个问题中询问结果。
我正在尝试了解 Erlang 和 F# 的性能特征。我发现 Erlang 的并发模型非常吸引人,但出于互操作性的原因,我倾向于使用 F#。虽然开箱即用的 F# 没有提供像 Erlang 的并发原语这样的东西——据我所知,async 和 MailboxProcessor 只涵盖了 Erlang 做得好的一小部分——我一直在试图了解 F# 性能的可能性明智的。
在 Joe Armstrong 的 Programming 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# 知之甚少,我尝试使用 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#,启动和杀死 100,000 个演员/处理器每个进程花费不到 2 微秒,大约比 Erlang 快 4 倍。也许更重要的是,我可以扩展到数百万个进程而不会出现任何明显的问题。启动 1 或 200 万个进程仍然需要每个进程大约 2 微秒。启动 2000 万个处理器仍然是可行的,但会减慢到每个进程大约 6 微秒。
我还没有花时间完全理解 F# 如何实现异步和 MailBoxProcessor,但这些结果令人鼓舞。我做错了什么吗?
如果没有,Erlang 是否在某些地方可能会胜过 F#?有什么理由不能通过库将 Erlang 的并发原语带到 F# 中?
编辑:由于布赖恩指出的错误,上述数字是错误的。当我修复它时,我会更新整个问题。