gpt4 book ai didi

concurrency - F# 在生成和终止进程方面真的比 Erlang 更快吗?

转载 作者:行者123 更新时间:2023-12-03 05:41:14 25 4
gpt4 key购买 nike

已更新:此问题包含一个错误,该错误使基准测试毫无意义。我将尝试一个更好的基准来比较 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 上,生成和终止 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# 中吗?

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

最佳答案

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

也就是说,我取得了一些成功;在我的盒子上,我可以执行 100,000 次,每次大约需要 25 秒。经历了太多之后,我想你可能会开始与分配器/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

综上所述,我不了解 Erlang,也没有深入思考是否有办法再精简 F#(尽管这很惯用)。

关于concurrency - F# 在生成和终止进程方面真的比 Erlang 更快吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2214954/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com