私は同時ユーザーの大規模な量を同時にメモリに表現することを可能にするシステムを設計しようとしています。このシステムを設計するために出発するとき、私は直ちにアーランに何らかのアクターベースの解決策を考えました。MailboxProcessorのパフォーマンスの問題
システムは、.NETで行う必要がありますので、私はMailboxProcessorを使用してF#でプロトタイプに取り組んで始めたが、それらの深刻なパフォーマンスの問題に実行しています。私の最初のアイデアは、1人のユーザーの通信をシリアル化するために、ユーザーごとに1人の俳優(MailboxProcessor)を使用することでした。
open System.Threading;
open System.Diagnostics;
type Inc() =
let mutable n = 0;
let sw = new Stopwatch()
member x.Start() =
sw.Start()
member x.Increment() =
if Interlocked.Increment(&n) >= 100000 then
printf "UpdateName Time %A" sw.ElapsedMilliseconds
type Message
= UpdateName of int * string
type User = {
Id : int
Name : string
}
[<EntryPoint>]
let main argv =
let sw = Stopwatch.StartNew()
let incr = new Inc()
let mb =
Seq.initInfinite(fun id ->
MailboxProcessor<Message>.Start(fun inbox ->
let rec loop user =
async {
let! m = inbox.Receive()
match m with
| UpdateName(id, newName) ->
let user = {user with Name = newName};
incr.Increment()
do! loop user
}
loop {Id = id; Name = sprintf "User%i" id}
)
)
|> Seq.take 100000
|> Array.ofSeq
printf "Create Time %i\n" sw.ElapsedMilliseconds
incr.Start()
for i in 0 .. 99999 do
mb.[i % mb.Length].Post(UpdateName(i, sprintf "User%i-UpdateName" i));
System.Console.ReadLine() |> ignore
0
ちょうど100kの俳優が私のクワッドコアi7の上で周り800ms取る作成:
私はこの問題、私は見ていますを再現するコードの小片を単離しました。次に、俳優のそれぞれにUpdateName
メッセージを送信し、俳優の完了を待つのに約1.8秒かかります。
は今、私はすべてのキューからオーバーヘッドが実現:MailboxProcessorの内部など、AutoResetEventsをリセット/設定、ThreadPoolの上INGの。しかし、これは本当に期待されるパフォーマンスですか? MailboxProcessorにMSDNや様々なブログの両方を読んでから、私はそれはErlangの俳優に親族ことだという考えを得ているが、abyssmalパフォーマンスから私は、これが現実に成立していないようです見ていますか?
また、8つのMailboxProcessorsを使用するコードの修正版を試してみましたが、それぞれがMap<int, User>
マップを保持していて、IDでユーザーを検索すると、UpdateName操作の合計時間が短縮されました1.2秒。しかし、それはまだ非常に遅いと感じ、変更されたコードはここにあります:
open System.Threading;
open System.Diagnostics;
type Inc() =
let mutable n = 0;
let sw = new Stopwatch()
member x.Start() =
sw.Start()
member x.Increment() =
if Interlocked.Increment(&n) >= 100000 then
printf "UpdateName Time %A" sw.ElapsedMilliseconds
type Message
= CreateUser of int * string
| UpdateName of int * string
type User = {
Id : int
Name : string
}
[<EntryPoint>]
let main argv =
let sw = Stopwatch.StartNew()
let incr = new Inc()
let mb =
Seq.initInfinite(fun id ->
MailboxProcessor<Message>.Start(fun inbox ->
let rec loop users =
async {
let! m = inbox.Receive()
match m with
| CreateUser(id, name) ->
do! loop (Map.add id {Id=id; Name=name} users)
| UpdateName(id, newName) ->
match Map.tryFind id users with
| None ->
do! loop users
| Some(user) ->
incr.Increment()
do! loop (Map.add id {user with Name = newName} users)
}
loop Map.empty
)
)
|> Seq.take 8
|> Array.ofSeq
printf "Create Time %i\n" sw.ElapsedMilliseconds
for i in 0 .. 99999 do
mb.[i % mb.Length].Post(CreateUser(i, sprintf "User%i-UpdateName" i));
incr.Start()
for i in 0 .. 99999 do
mb.[i % mb.Length].Post(UpdateName(i, sprintf "User%i-UpdateName" i));
System.Console.ReadLine() |> ignore
0
私の質問はここにあります、私は何か間違っていますか? MailboxProcessorをどのように使用するのか分からなかったのですか?または、このパフォーマンスが期待されるものです。
更新:
だから私はsprintfのを使用することが非常に遅いです、そして、それは結局のところ場所なことを私に伝えている、irc.freenode.net @ fsharp ##の上にいくつかの男のホールドを得ました私のパフォーマンスの問題の大部分はから来ていた。しかし、上記のsprintf操作を削除して、すべてのユーザーに同じ名前を使用するだけで、私はまだ実際に遅く感じる作業を約400msで終了します。
sprintfが遅い場合は、生産システムでかなりのパフォーマンス/ –
でパフォーマンスを向上させる可能性がある新しいF#3.1を試すことができます。ユーザーごとにエージェントを起動する可能性が高くなります。同様のメモで、すべてのユーザーが同時にメッセージを投稿すると思われますか?ルックアップアクセスが 'Seq.initInfinite'を使用して、エージェント –
あなたは 'Array.init'を使っていましたが、これはあなたの作成時間コストの一部として表示されます。 –