我一直在試圖設計一個系統,它允許大量的併發用戶同時在內存中表示。當開始設計這個系統時,我立刻想到了某種基於演員的解決方案,即Erlang的一個親屬。郵箱處理器性能問題
該系統必須在.NET中完成,因此我開始使用MailboxProcessor在F#中處理原型,但遇到了嚴重的性能問題。我最初的想法是爲每個用戶使用一個actor(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秒。
現在,我意識到所有隊列都有開銷:在ThreadPool上,在MailboxProcessor內部設置/重置AutoResetEvents等。但這真的是預期的表現嗎?從閱讀MSDN和MailboxProcessor上的各種博客,我都明白這是erlang演員的親戚,但是從我看到的這種深淵表現看來,這似乎並不適用於現實?
我也嘗試了代碼,它使用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應該如何使用?或者這是預期的表現。
更新:
所以我得到了## fsharp @ irc.freenode.net,它告訴我,使用的sprintf很慢一些球員的保持,事實證明這是一個地方我的大部分表現問題都來自於。但是,刪除上面的sprintf操作,併爲每個用戶使用相同的名稱,我仍然最終得到大約400ms的操作,這感覺非常慢。
如果sprintf速度很慢 - 您可以嘗試新的F#3.1,這顯然可以提高生產系統的性能達到顯着水平/ –
也許您更有可能按需啓動代理,這可能意味着啓動時間這不是一個大問題,在類似的筆記中,你是否期望所有用戶同時發佈消息? –
您還可以通過使用一個可變的字典在一個不變的地圖,爲查找訪問是通過代理 –