2

複数のビデオ クリップをフレームごとに処理するアプリケーションを並列化したいと考えています。クリップごとの各フレームのシーケンスは重要です (明らかに)。TPL Dataflow を使用することにしたのは、これがデータフロー (ムービー フレームがデータである) の良い例であると信じているからです。

したがって、データベースからフレームをロードするプロセスが 1 つあります (たとえば、500 のバッチで、すべてまとめて)

Example sequence:    
|mid:1 fr:1|mid:1 fr:2|mid:2 fr:1|mid:3 fr:1|mid:1 fr:3|mid:2 fr:2|mid:2 fr:3|mid:1 fr:4|

それらを BufferBlock に投稿します。この BufferBlock に、ActionBlocks をフィルターにリンクして、MovieID ごとに 1 つの ActionBlock を持つようにしました。これにより、ある種のデータ パーティショニングが得られます。各 ActionBlock はシーケンシャルですが、理想的には、複数のムービーの複数の ActionBlock を並行して実行できます。

上記のネットワークは機能しており、並行して実行されていますが、私の計算では、同時に実行されている ActionBlock は 8 ~ 10 個にすぎません。各 ActionBlock の実行時間とその約 100 ~ 200 ミリ秒の時間を計りました。少なくとも 2 倍の同時実行性を実現するには、どのような手順を実行できますか?

アクション デリゲートを非同期メソッドに変換し、ActionBlock アクション デリゲート内でデータベース アクセスを非同期にしようとしましたが、役に立ちませんでした。

編集:追加レベルのデータ パーティショニングを実装しました。奇数 ID のムービーのフレームは ServerA で処理され、偶数ムービーのフレームは ServerB で処理されます。アプリケーションの両方のインスタンスが同じデータベースにヒットしました。問題が DB IO である場合、処理されたフレーム数の合計に改善は見られません (または 20% 未満の非常にわずかなもの)。しかし、私はそれが倍増していると見ています。したがって、これにより、Threadpool はより多くのフレームを並行して実行するために、より多くのスレッドを生成していないと結論付けることができます (両方のサーバーはクアッドコアであり、プロファイラーはアプリケーションごとに約 25 ~ 30 のスレッドを示します)。

4

3 に答える 3

2

いくつかの仮定:

  • サンプルデータから、ムービーフレーム (およびおそらくムービー内のフレーム) を順不同で受信しています

  • あなたのActionBlock<T>インスタンスは一般的です。それらはすべて処理のために同じメソッドを呼び出します。次のように、各ムービー ID に基づいてそれらのリストを作成するだけです (事前にムービー ID のリストがあります)。

// The movie IDs
IEnumerable<int> movieIds = ...;

// The actions.
var actions = movieIds.Select(
    i => new { Id = i, Action = new ActionBlock<Frame>(MethodToProcessFrame) });

// The buffer block.
BufferBlock<Frame> buffer = ...;

// Link everything up.
foreach (var action in actions) 
{
    // Not necessary in C# 5.0, but still, good practice.
    // The copy of the action.
    var actionCopy = action;

    // Link.
    bufferBlock.LinkTo(actionCopy.Action, f => f.MovieId == actionCopy.Id);
}

この場合、ActionBlock<T>作業が行われていないインスタンスを作成しすぎています。フレーム (および場合によってはムービー) が順不同であるため、すべてのActionBlock<T>インスタンスで処理が行われるとは限りません。

さらに、ActionBlock<T>インスタンスを作成すると、MaxDegreeOfParallelism1 の で作成されます。つまり、同時にブロックにアクセスできるスレッドは 1 つだけなので、スレッド セーフです。

Task<TResult>さらに、TPL DataFlow ライブラリは最終的にクラスに依存しており、デフォルトではスレッド プールでスケジュールされます。スレッド プールは、ここでいくつかのことを行います。

  • すべてのプロセッサ コアが飽和していることを確認します。これは、インスタンスが飽和していることを確認することとは大きく異なります。これは、考慮すべきメトリックです。ActionBlock<T>

  • プロセッサ コアが飽和している間は、作業が均等に分散されていることを確認し、同時実行タスクが多すぎないようにしてください (コンテキスト スイッチはコストがかかります)。

また、ムービーを処理するメソッドは一般的であるように見えます。また、どのムービーからどのフレームが渡されるかは問題ではありません (問題がある場合、多くのことが変更されるため、質問を更新する必要があります)。 )。これは、スレッドセーフであることも意味します。

また、1 つのフレームの処理が前のフレームの処理に依存していないと想定できる場合 (または、映画のフレームが順番に並んでいるように見える場合)、単一 の値を使用できますが、値をActionBlock<T>微調整します。MaxDegreeOfParallelismそのようです:

// The buffer block.
BufferBlock<Frame> buffer = ...;

// Have *one* ActionBlock<T>
var action = new ActionBlock<Frame>(MethodToProcessFrame,
    // This is where you tweak the concurrency:
    new ExecutionDataflowBlockOptions {
        MaxDegreeOfParallelism = 4,
    }
);

// Link.  No filter needed.
bufferBlock.LinkTo(action);

今、あなたのActionBlock<T>意志は常に飽和しています。確かに、責任のあるタスク スケジューラ (デフォルトではスレッド プール) は依然として同時実行の最大量を制限しますが、同時に合理的に実行できる限りのことを実行します。

そのために、アクションが真にスレッド セーフである場合は、次のようMaxDegreeOfParallelismに をに設定できます。DataflowBlockOptions.Unbounded

// Have *one* ActionBlock<T>
var action = new ActionBlock<Frame>(MethodToProcessFrame,
    // This is where you tweak the concurrency:
    new ExecutionDataflowBlockOptions {
        // We're thread-safe, let the scheduler determine
        // how nuts we can go.
        MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded,
    }
);

もちろん、これはすべて、他のすべてが最適であることを前提としています (I/O 読み取り/書き込みなど)。

于 2012-11-15T19:00:33.680 に答える
-1

おそらく、それが最適な並列化の度合いです。スレッドプールは、アクティブにする実際のスレッドの最適な数を決定するのに非常に優れています。私の推測では、あなたのハードウェアは、実際に並行して動作する多くの並行プロセスをサポートできると思います。さらに追加すると、実際にはスループットが向上しません。スレッド間のコンテキスト切り替えに費やす時間が増え、実際に作業する時間が減ります。

長期間にわたって、CPU 負荷、メモリ バス、ネットワーク接続、ディスク アクセスなどのすべてが処理能力を下回っていることに気付いた場合は、問題が発生している可能性があります。実はボトルネック。どこかのリソースが容量に達している可能性がありますが、TPL はそれを認識し、そのリソースが過飽和にならないようにしています。

于 2012-11-14T17:01:58.280 に答える
-1

あなたはIOに縛られていると思います。問題はどこですか?読み取り時または書き込み時。読み取りよりも多くのデータを書き込んでいますか。高速に書き出すことができないため、CPU が 50% 未満になることがあります。

ActionBlock が間違っていると言っているわけではありませんが、BlockingCollection を使用したプロデューサー コンシューマーを検討します。データの読み書き方法を最適化します。

これは異なりますが、テキストのブロックを読むアプリがあります。テキストを解析し、単語を SQL に書き戻します。シングル スレッドで読み取り、解析を並列処理し、シングル スレッドで書き込みます。インデックスを破壊しないように、シングル スレッドで書き込みます。IO バウンドの場合は、最も遅い IO を特定し、そのプロセスを最適化する必要があります。

その IO について詳しく教えてください。

質問では、データベースからの読み取りについても言及しています。
BlockingCollections を試してみます。
BlockingCollection Class
メモリを吹き飛ばさないように、それぞれにサイズ制限があります。
(ほとんど)空にならないように十分な大きさにします。
最も遅いステップの後のブロッキング コレクションは空になります。並列処理ができる場合は、そうしてください。
私が見つけたのは、テーブルへの並列挿入は高速ではないということです。
1 つのプロセスをロックして保持し、そのホースを開いたままにします。
挿入方法をよく見てください。
一度に 1 つの行は遅いです。
私は TVP を使用して一度に 10,000 を挿入しますが、多くの人は Drapper や BulkInsert を好みます。
インデックスとトリガーを削除し、クラスター化されたインデックスで並べ替えて挿入すると、最速になります。タブロックを取り、それを保持します。10 ミリ秒の範囲で挿入されています。
現在、更新は最も遅いです。それを見てください - 一度に 1 つの行だけを実行していますか?
タブロックの撮影とビデオクリップでの実行をご覧ください。
醜い更新でない限り、挿入よりも時間がかかることはありません。

于 2012-11-14T18:52:18.117 に答える