質問

ポートとレシーバーを介して 15 の非同期操作をチェーンしています。このため、スレッド間のメッセージング時間、特にタスクがデータをポートにポストしてから、新しいタスクが別のスレッドで同じデータの処理を開始するまでにかかる時間に非常に懸念が残りました。各スレッドが開始時にアイドル状態であるという最良の状況を想定して、ストップ ウォッチ クラスを使用して、単一スレッドで最高の優先順位で動作する 2 つの異なるディスパッチャからの時間を測定するテストを生成しました。

驚いたことに、私の開発機器は Windows 7 x64 を実行する Q6600 クアッドコア 2.4 Ghz コンピューターであり、テストでの平均コンテキスト スイッチ時間は 5.66 マイクロ秒、標準偏差は 5.738 マイクロ秒、最大で 1.58 ミリ秒近くでした ( 282 倍です!)。ストップウォッチの周波数は 427.7 ナノ秒なので、センサーのノイズはまだ十分にあります。

私がやりたいのは、スレッド間のメッセージング時間をできる限り短縮することであり、同様に重要なことは、コンテキスト スイッチの標準偏差を減らすことです。Windows がリアルタイム OS ではないことは承知しており、保証はありませんが、Windows スケジューラは公平なラウンドロビンの優先順位ベースのスケジュールであり、このテストの 2 つのスレッドは両方とも最高の優先順位になっています (優先順位が最も高いスレッドのみです)。高い)ので、スレッド上でコンテキストの切り替えが行われないようにする必要があります(最大時間が 1.58 ミリ秒であることから明らかです...Windows クォンタは 15.65 ミリ秒だと思います?) 私が考えられる唯一のことは、スレッド間でメッセージを渡すために CCR によって使用されるロック メカニズムに対する OS 呼び出しのタイミングの変動です。

他にスレッド間メッセージング時間を測定した人がいて、それを改善する方法について提案がある場合は、私に知らせてください。

私のテストのソースコードは次のとおりです。

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Microsoft.Ccr.Core;

using System.Diagnostics;

namespace Test.CCR.TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting Timer");
            var sw = new Stopwatch();
            sw.Start();

            var dispatcher = new Dispatcher(1, ThreadPriority.Highest, true, "My Thread Pool");
            var dispQueue = new DispatcherQueue("Disp Queue", dispatcher);

            var sDispatcher = new Dispatcher(1, ThreadPriority.Highest, true, "Second Dispatcher");
            var sDispQueue = new DispatcherQueue("Second Queue", sDispatcher);

            var legAPort = new Port<EmptyValue>();
            var legBPort = new Port<TimeSpan>();

            var distances = new List<double>();

            long totalTicks = 0;

            while (sw.Elapsed.TotalMilliseconds < 5000) ;

            int runCnt = 100000;
            int offset = 1000;

            Arbiter.Activate(dispQueue, Arbiter.Receive(true, legAPort, i =>
                                                                            {
                                                                                TimeSpan sTime = sw.Elapsed;
                                                                                legBPort.Post(sTime);
                                                                            }));
            Arbiter.Activate(sDispQueue, Arbiter.Receive(true, legBPort, i =>
                                                                             {
                                                                                 TimeSpan eTime = sw.Elapsed;
                                                                                 TimeSpan dt = eTime.Subtract(i);
                                                                                 //if (distances.Count == 0 || Math.Abs(distances[distances.Count - 1] - dt.TotalMilliseconds) / distances[distances.Count - 1] > 0.1)
                                                                                 distances.Add(dt.TotalMilliseconds);

                                                                                 if(distances.Count > offset)
                                                                                 Interlocked.Add(ref totalTicks,
                                                                                                 dt.Ticks);
                                                                                 if(distances.Count < runCnt)
                                                                                     legAPort.Post(EmptyValue.SharedInstance);
                                                                             }));


            //Thread.Sleep(100);
            legAPort.Post(EmptyValue.SharedInstance);

            Thread.Sleep(500);

            while (distances.Count < runCnt)
                Thread.Sleep(25);

            TimeSpan exTime = TimeSpan.FromTicks(totalTicks);
            double exMS = exTime.TotalMilliseconds / (runCnt - offset);

            Console.WriteLine("Exchange Time: {0} Stopwatch Resolution: {1}", exMS, Stopwatch.Frequency);

            using(var stw = new StreamWriter("test.csv"))
            {
                for(int ix=0; ix < distances.Count; ix++)
                {
                    stw.WriteLine("{0},{1}", ix, distances[ix]);
                }
                stw.Flush();
            }

            Console.ReadKey();
        }
    }
}
役に立ちましたか?

解決

Windows はリアルタイム OS ではありません。しかし、あなたはすでにそれを知っていました。問題となるのはコンテキストの切り替え時間であり、必ずしもメッセージ時間ではありません。プロセス間通信がどのように機能するかを実際には指定していません。本当に複数のスレッドを実行しているだけの場合は、通信プロトコルとして Windows メッセージを使用せず、代わりにアプリケーション ホスト型メッセージ キューを使用して独自の IPC をローリングしてみてください。

Windows のどのバージョンでも、コンテキストの切り替えが発生した場合に期待できる最良の平均は 1 ミリ秒です。おそらく、アプリケーションがカーネルに屈服しなければならない時間は 1 ミリ秒であると思われます。これは、Ring-1 アプリケーション (ユーザー空間) の設計によるものです。1ms 未満にすることが絶対的に重要な場合は、アプリケーションの一部を Ring-0 に切り替える必要があります。これは、デバイス ドライバーを作成することを意味します。

デバイス ドライバーは、ユーザー アプリのようなコンテキスト切り替え時間に悩まされず、ナノ秒解像度のタイマーやスリープ コールにもアクセスできます。これを行う必要がある場合は、DDK (デバイス ドライバー開発キット) が Microsoft から無料で入手できますが、サードパーティの開発キットに投資することを強くお勧めします。通常、非常に優れたサンプルと、適切に設定するためのウィザードが多数用意されており、DDK ドキュメントを読んで発見するまでに何か月もかかります。通常の Visual Studio デバッガーではデバイス ドライバーのデバッグができないため、SoftIce なども入手することをお勧めします。

他のヒント

15の非同期操作は、のHAVE のは、非同期にありますか?すなわち、あなたには、いくつかのライブラリの制限によって、このように動作することを余儀なくされている、またはあなたが同期呼び出しを行うための選択肢を持っているのですか?

あなたは選択肢を持っている場合は、

、あなたは非同期の使用は設定パラメータによって制御されるようにアプリケーションを構築する必要があります。同じスレッドに戻り同期動作対異なるスレッドに戻り、非同期動作との違いは、コードに透明であるべきです。こうすることで、コードの構造を変更せずに調整することができます。

句「あきれる平行」作業の大部分が行われているアルゴリズムを説明は明らかに無関係であるので、それが簡単に並列化すること、任意の順序で行うことができます。

しかし、あなたは、「ポートおよび受信機を通じて15の非同期操作を一緒にチェーン化」されています。これは、「あきれるほどシーケンシャル」として記述することができます。言い換えれば、同じプログラムは、論理的に単一のスレッドに書くことができます。しかし、その後、あなたは(重要性のいずれかがあると仮定して)非同期操作の間のCPUバウンドのワークoccuringのための任意の並列性を失うと思います。

あなたは、有意なCPUバウンドの仕事を切り出し、ただ時間をコンテキストスイッチを測定するための簡単なテストを書く場合は、あなたが発見したとして、あなたは、コンテキストの切り替え時間の変化を測定することになるだろう、どうなったと思いますます。

あなたが行うにはCPUの仕事を大量に持っているので、複数のスレッドを実行するための唯一の理由は、そしてあなたは、いくつかのCPU間でそれを共有したいです。計算の個々のチャンクは短命十分にある場合は、コンテキストスイッチは、のいずれかののOS上の大きなオーバーヘッドとなります。それぞれが非常に短く、ダウン15の段階にあなたの計算を壊すことによって、あなたは基本的に不要なコンテキストスイッチの多くを行うためにOSを求めています。

ThreadPriority.Highest は、スレッド スケジューラ自体のみが高い優先順位を持つことを意味するわけではありません。Win32 API には、より詳細なレベルのスレッド優先度があります (クリック感のある) 最高より数レベル高い (IIRC 最高は通常、管理者以外のコードを実行できる最高の優先順位であり、管理者は他のハードウェア ドライバー/カーネル モード コードと同様に、より高い優先順位をスケジュールできます)。そのため、プリエンプトされないという保証はありません。 。

スレッドが最も高い優先順位で実行されている場合でも、より高い優先順位のスレッドが必要とするリソース ロックを保持している場合、Windows は他のスレッドを基本優先順位よりも上位に昇格させることができます。これもコンテキスト スイッチが発生する可能性があります。

それでも、あなたが言うように、Windows はリアルタイム OS ではなく、とにかくスレッドの優先順位を尊重するという保証はありません。

この問題に別の道を攻撃するためには、非常に多くのデカップリング非同期操作を持っている必要がありますか?考えることが有用であり得る:垂直作業を分割(非同期エンドへのデータエンドのnumCoresチャンクを処理)の代わりに、水平(15の切り離さ段階で処理されたデータの各チャンクと、今のように)作業を分割します。同期より小さな数に合計を減らすために、あなたの15回のステージの一部を結合ます。

スレッド間通信のオーバーヘッドが常に非自明であろう。あなたの15回の操作のいくつかは仕事のほんのチャンクを行っている場合は、コンテキストスイッチはあなたを噛まされます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top