2

次の問題を解決しようとしています。数ミリ秒の大きなハートビートでリアルタイムで実行されているエージェントがいくつかありますが、この理由でエージェントが処理する操作の順序はほとんど決定論的です (メッセージ処理がボトルネックではないため)。

現在、ハートビートがなくなったシステムの大量のシミュレーションを実行しています (そうでなければ、数世紀かかります)。ただし、操作の順序が保持されていることを確認する必要があります。このために、私は次の解決策を採用しました。シミュレーターは、ダミーの同期メッセージを投稿し、応答を待っている間にブロックすることで、各エージェントがメッセージ キューを処理したことを確認します。これは私のアプリケーションでは機能しますが、かかる時間は直感的ではありません-シングルスレッドの実装は桁違いに高速になるためです(テストしていませんが、x 100程度だと思います)。

別のライブラリ akka.net を使用しようとしても、問題を示す小さなテストを分離しました

type Greet = 
| Greet of string
| Hello of AsyncReplyChannel<bool>
| Hello2  

[<EntryPoint>]
let main argv =
    let system = System.create "MySystem" <| Configuration.load()    
    let greeter = spawn system "greeter" <| fun mailbox ->
        let rec loop() = actor {
            let! msg = mailbox.Receive()
            let sender = mailbox.Sender()
            match msg with
                | Greet who -> () // printf "Hello, %s!\n" who
                | Hello2 -> sender.Tell(true)
                | _ -> ()
            return! loop()
            }
        loop()

    let greeterF =
        MailboxProcessor.Start
            (fun inbox ->                
                async {
                    while true do
                        let! msg = inbox.Receive()
                        match msg with
                        | Greet who -> () // printf "Hello, %s!\n" who
                        | Hello reply -> reply.Reply true
                        | _ -> ()
                    }
            )

    let n = 1000000

    let t1 = System.Diagnostics.Stopwatch()
    t1.Start()
    for i = 1 to n do
        let rep = greeterF.PostAndReply(fun reply -> (Hello reply)) |> ignore
        ()

    printfn "elapsed Mailbox:%A" t1.ElapsedMilliseconds

    t1.Restart()

    for i = 1 to n do        
        let res = greeter.Ask (Hello2)
        let rr = res.Result
        ()

    printfn "elapsed Akka:%A" t1.ElapsedMilliseconds
    System.Console.ReadLine () |> ignore

    0

基本的に、どちらもわずか 100 万回の同期に約 10 秒かかります。これは計算に関係なく、残念なことです。

誰かが同じ問題に遭遇したかどうか、そしてオーバーヘッドをオフにしてすべてをシングルスレッドモードで実行させる方法があるかどうか疑問に思っています...すべてのCPUを非アクティブ化するよりも優れていますが、BIOSで1つ-またはエージェントなしでシステム全体のクローンを作成します。

どんな助けでも大歓迎です。

4

3 に答える 3

2

わずかに変更されたMailboxProcessorバージョンを次に示します。

module MBPAsync =
  type Greet = 
   | Greet of string
   | Hello of AsyncReplyChannel<bool>

  let run n =
    let timer = Stopwatch.StartNew ()

    use greeter =
      MailboxProcessor.Start <| fun inbox -> async {
        while true do
          let! msg = inbox.Receive()
          match msg with
           | Greet who -> () // printf "Hello, %s!\n" who
           | Hello reply -> reply.Reply true
      }

    Async.RunSynchronously <| async {
      for i = 1 to n do
        do! Async.Ignore (greeter.PostAndAsyncReply Hello)
    }

    let elapsed = timer.Elapsed
    printfn "%A" elapsed

ここでの違いは、このバージョンPostAndAsyncReplyでは非同期ワークフローで計算を使用および保持することです。私の簡単なテストでは、これは を使用するよりもはるかに高速であるように見えましたPostAndReplyが、YMMV.

上記の MBP バージョンから取得したタイミングは、おおよそ次のようになります。

> MBPAsync.run 1000000 ;;
00:00:02.6883486
val it : unit = ()

以前のコメントで、私の Hopac ライブラリについて言及しました。これは、Hopac を使用して最適化されたバージョンです。

module Hop =
  type Greet = 
   | Greet of string
   | Hello of IVar<bool>

  let run n =
    let timer = Stopwatch.StartNew ()

    let greeterCh = ch ()
    do greeterCh >>= function
          | Greet who -> Job.unit ()
          | Hello reply -> reply <-= true
       |> Job.forever
       |> server

    Job.forUpToIgnore 1 n <| fun _ ->
        let reply = ivar ()
        greeterCh <-- Hello reply >>.
        reply
    |> run

    let elapsed = timer.Elapsed
    printfn "%A" elapsed

上記のHopacバージョンから取得したタイミングは、おおよそ次のようになります。

> Hop.run 1000000 ;;
00:00:00.1088768
val it : unit = ()
于 2015-04-16T05:09:11.750 に答える
1

私は F# 開発者ではありませんが、Akka.NET のコア開発者です。あなたのシナリオのためのいくつかのアイデア:

  1. この作業にアクターを 1 つだけ使用している場合はPinnedDispatcher、アクターが常に独自の専用スレッドで実行されるように、アクターを使用してみてください。これにより、不要なコンテキスト切り替えのオーバーヘッドを節約できます。

  2. メールボックスのスループットをPinnedDispatcher通常の設定よりもはるかに高く設定することもできます。つまり、通常の 25 ではなく 10000 (または何か) のスループット値を設定します。メールボックスの内容が急激に大きくなると仮定すると、これによりメールボックス同期のオーバーヘッドが節約されます。

ディスパッチャーの構成は次のようになります。

 my-pinned-dispatcher {
      type = PinnedDispatcher
      throughput = 1000 #your mileage may vary
 }

そして、それを使用するようにアクターを構成します

C# 流暢なインターフェイス

var myActor = myActorSystem.ActorOf(Props.Create<FooActor>()
.WithDispatcher("my-pinned-dispatcher");

設定

akka.actor.deployment{
   /greeter{
     dispatcher = my-pinned-dispatcher
   }
}

これらは両方とも、App.config または Web.config の HOCON を介して構成できるオプションです。または、Propsクラスの流暢なインターフェイスを使用してこれを行うことができます。また、注目に値すること:現時点では固定されたディスパッチャにバグがありますが、来週リリースされる次のメンテナンス リリース (v1.0.1) で修正される予定です。

あなたのマイレージは異なるかもしれませんが、これは私が試みるものです - 基本的には、単一のアクターに関する競合とオーバーヘッドを減らすのに役立つように設計されています.

于 2015-04-15T20:20:23.117 に答える