web-dev-qa-db-ja.com

並行キューでのパフォーマンスの低下

私は2つのスレッドを持っています。 1つは、シリアルポートからデータを収集し、それを配列に配置して、並行キューに追加することです。もう1つのスレッドは、これらの配列を取得して、データをリアルタイムでプロットしています。毎秒50バイトのデータの約150パケットがあります。私が抱えている問題は、これを実行すると、約10%のCPUを消費していることです。そして、これは非常に高速なコアI7Haswell上にあります。消費スレッドにThread.Sleep(1)を追加すると、CPU使用率が1%に低下します。問題は、これをThread.Sleep(2)に置くと、パケットが同期されなくなることです。したがって、これは解決策ではなく、低速のコンピューターで実行した場合、おそらく機能しません。

コードは単純です。これがプロデューサースレッドです:

    static void FillQueue(byte[] buffer)
    {
        dataQueue.Enqueue(buffer);
    }

そしてここに消費者スレッドがあります:

        while (continuePolling)
        {                
            Thread.Sleep(1); //if removed, there is 10% CPU utilization.  If higher then packet synchronization is lost.

            if (dataQueue.TryDequeue(out result))
            {
                ProcessPacket(result);
            }               
        }

ProcessPacketメソッドの実行には約0.3ミリ秒かかり、1秒間に約10回呼び出されるため、これを理解するのは難しいと思います。

代わりにブロッキングコレクションを使用してみました。こちらがプロデューサーです

 BlockingCollection<byte[]> dataQueue = new BlockingCollection<byte[]>;

 public static void addData(buffer)
 {
      dataQueue.add(buffer);
 }

そしてここに消費者がいます

 while (dataQueue.TryTake(out result)
 {
     ProcessPacket(result);
 }

全く違いはありません! 10%のCPUを使用し、Thread.Sleep(1)を追加しても問題ありませんが、Thread.Sleep(2)を使用すると、パケットが失われます。各パケットには50バイトがあります。それでおしまい。そして、それらは生産されるのと同じくらい速く消費されます。ありがとう

8
Tom

これは、基本的にwhile (true) {}ループがあるためです。 ProcessPacketは0.3ミリ秒かかり、1秒あたり10パケットあるため、1秒あたり3ミリ秒の有用な作業があります。残りの997msは、キューに新しいアイテムがあるかどうかをループが常にチェックし、CPUリソースを浪費します。

代わりに、キューと一緒にBlockingCollectionを使用してください。これにより、タスクにはるかに優れたオプションが提供されます。デキューのブロック(必要に応じてタイムアウトとキャンセルトークンを使用)をサポートしているため、CPUを浪費しません。

var dataQueue = new BlockingCollection<string>(new ConcurrentQueue<string>());
// Add instead of Enqueue
dataQueue.Add("some item");
// Take instead of Dequeue, this will block until item is available in queue
var result = dataQueue.Take();
// blocks until item is available or timeout happens
if (dataQueue.TryTake(out result, TimeSpan.FromMilliseconds(100)))

また、「ストリーミング」インターフェイスもサポートしているため、コードは次のようになります。

foreach (var result in dataQueue.GetConsumingEnumerable()) {
    ProcessPacket(result);        
}

そして、continuePollingフラグの代わりに-呼び出します

dataQueue.CompleteAdding();

これ以上アイテムが期待されない場合(つまり、continuePollingをfalseに設定する場合)-これにより、GetConsumingEnumerableがアイテムの提供を終了して戻ります。

11
Evk