3度目の正直ParallelPingShooter
あっちで色々と考えて実際実装してみたけど、どうもコレジャナイ感が強いし、多分、きっと動くけどLock多用してる上にLockの内方でループ回したりリソースの移動させちゃったりするもんだから、早々にそこら辺で律速するなこいつ的な感じがあった。とはいえ、うまい実装も思い浮かばなかったので惰性で実装していた。そんな中、寝しなに思い浮かんでこれいけそうじゃね?とした物を時計⌚屋の開店準備中に脳内CLRで走らせてなんとなくうまくいきそうかつ、もうちょい何とかしようがありそうだったので忘れないウチにまとめておこうかなって
使ってる技術
C# 9.0->10へ
.NET 5.0->6.0へ
Channel
見えてるプレイヤー
Coordinator
Pingをぶっ放す先を文字通りコーディネートする
後述するShooterにぶっ放すことが出来るHostDatumを振り出す責務を持つ
1つのインスタンスのみ存在する
Shooter
Pingをぶっ放す
ぶっ放した結果をChannel経由で外部へ通知する
CoordinatorからもらったHostDatumを使用後、確実にCoordinatorに返却する責務を持つ
複数のインスタンスが複数のスレッド上で稼働している
HostDatum
上記2つの間をやりとりするためのToken的存在
Pingをぶっ放す先のIpAddrを持ってる
連続実行をさけるための遅延を管理する
上記に加えて前回がTimeoutした場合、遅延時間を天井まで増加させることを管理する
Coordinatorの実装
ConcurrentQueueを元にして実装
見かけ上優先付きにしたいので、TimedOut、Default、PriorityのQueueを持つ。
TimedOutは文字通り前回時間切れになったホストが入る(優先度最低)
Defaultは正常完了後のHostDatumが入る(優先度通常
PriorityはDefaultで一度候補に挙がったが、遅延待機中で実行されなかったHostDatumが入る(優先度最高
選択の手法
前提条件
並列実行を許容する
ろっくふりー
多少の選択不整合性には目をつぶる
実際の挙動
ArrayPoolからBufferを取得
優先順位順にQueueをEnqueueしていく。
HostがActivatableなら降りだして完了。
WaitingだったらBufferに放り込んで、またEnqueue
Queueが空になる、またはActivatableな物を見つけたらなったらBufferにためてたHostDatumを当該Queueに戻す
この操作を優先度順に行っていく
全てScanしても無ければTimedOutQueueを含まないRemainTimeのArithmeticAveを計算して待機時間としてShooter側に通知する。
検討事項
TimedOutは分離すべきか?
対象ホストに対してShooterの数が明らかに過小なとき、TimedOutQueueを参照するチャンスが皆無になりかねない
2^n回に一回見に行くなど対策してみる(Interlocked.Increment+BitMak使えばかなりローコストで判定できる)
現状、無駄な複雑化と判断してTimedOutQueueの実装見送り
Shooter側の数量管理は今のところ考えてないけど何れはやってみたい
CPUバウンダリの話だと過剰なクライアント稼働させても各々の最実行時Delayが増加していくのであんまり負担は出ない
ただ、メモリインパクトは相応に増えると思うけど
ついでに、ろっくふりーとはなってるけど、CoordinatorがCentralRepository的な扱いなのでさばききれない天井はそんなに高くないと思う(とはいえ、Lockしてるよっか大分マシだと思うけど)
ただ、NICの限界がCPUバウンダリの限界より先に来そうな趣ふかしw