3

ポートとレシーバーを介して 15 の非同期操作を連鎖させています。これにより、スレッド間のメッセージング時間、特にタスクがデータをポートに投稿してから、新しいタスクが別のスレッドで同じデータの処理を開始するまでの時間に非常に懸念を抱くようになりました。各スレッドが開始時にアイドル状態であるという最良の状況を想定して、ストップ ウォッチ クラスを使用して、それぞれが 1 つのスレッドで最高の優先度で動作する 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 ミリ秒だと思いますか?) 私が考えることができる唯一のことは、OS 呼び出しのタイミングの変動です。スレッド間でメッセージを渡すために CCR によって使用されるロック メカニズムに。

他の誰かがスレッド間メッセージング時間を測定し、それを改善する方法について何か提案があれば教えてください。

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

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();
        }
    }
}
4

4 に答える 4

2

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

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

デバイスドライバーは、ユーザーアプリと同じコンテキスト切り替え時間に悩まされることはなく、ナノ秒の解像度のタイマーとスリープ呼び出しにもアクセスできます。これを行う必要がある場合は、DDK(デバイスドライバー開発キット)をMicrosoftから無料で入手できますが、サードパーティの開発キットに投資することを強くお勧めします。彼らは通常、本当に良いサンプルとたくさんのウィザードを持っており、DDKドキュメントを読んで発見するのに何ヶ月もかかるようなものを正しくセットアップします。また、通常のVisual Studioデバッガーはデバイスドライバーのデバッグに役立たないため、SoftIceのようなものを入手することもできます。

于 2009-09-29T14:30:50.947 に答える
2

15 の非同期操作は非同期でなければなりませんか? つまり、一部のライブラリの制限によってこの方法で操作することを余儀なくされていますか、それとも同期呼び出しを行う選択肢がありますか?

選択できる場合は、非同期性の使用が構成パラメーターによって制御されるように、アプリケーションを構造化する必要があります。異なるスレッドで返される非同期操作と同じスレッドで返される同期操作の違いは、コード内で透過的である必要があります。そうすれば、コード構造を変更せずに調整できます。

「恥ずかしいほど並列」というフレーズは、実行される作業の大部分が明らかに独立しているため、任意の順序で実行できるため、並列化が容易になるアルゴリズムを表しています。

しかし、あなたは「ポートとレシーバーを介して15の非同期操作を連鎖させています」。これは「恥ずかしいほど連続」と表現できます。つまり、同じプログラムを単一のスレッドで論理的に記述することができます。ただし、非同期操作の間に発生する CPU バウンドの作業の並列性が失われます (何らかの重要性があると仮定します)。

重要な CPU バウンドの作業を省略し、コンテキストの切り替え時間を測定するだけの簡単なテストを作成すると、ご想像のとおり、コンテキストの切り替え時間の変動を測定することになります。

複数のスレッドを実行する唯一の理由は、CPU が実行する大量の作業があり、複数の CPU 間で共有したいからです。計算の個々のチャンクが十分に短命である場合、コンテキストの切り替えは、どのOS でも大きなオーバーヘッドになります。計算をそれぞれ非常に短い 15 段階に分割することで、基本的に OS に多くの不必要なコンテキスト スイッチングを要求することになります。

于 2009-10-03T09:04:28.510 に答える
0

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

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

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

于 2009-09-16T23:48:11.487 に答える
0

この問題に別の方法で取り組むには、これほど多くの分離された非同期操作が必要ですか? 作業を水平に分割する (現在のように、データの各チャンクを 15 の分離された段階で処理する) 代わりに、作業を垂直に分割する (numCores 個のデータのチャンクをエンドツーエンドで非同期に処理する)。15 ステージの一部を同期的に結合して、合計をより少ない数に減らします。

スレッド間通信のオーバーヘッドは常に重要です。15 の操作の一部がほんの少しの作業しか行っていない場合、コンテキストの切り替えに悩まされます。

于 2009-09-29T13:48:26.287 に答える