2

並列でスレッドセーフな計算と並列を組み合わせるパターンはありますか?

最初のステップが並列の恩恵を受ける結果を計算する必要があり、2 番目のステップは並列の結果に対するシリアル プロセスです。

1 つのオプションは、並行して実行し、出力をコレクションに保存してから、コレクションを順次処理することです。コレクションが非常に大きくなる可能性があるため、メモリ管理に問題があります。

以下はシリアル版です。基本的に、TableQueryGetRowKeys を並列化し、その結果をスレッド セーフな方法で使用したいと考えています。for を並列化し、最終結果をロックしようとしましたが、rowKeys がオフになる可能性があります。集計を試みましたが、集計でスレッドセーフな交差を実行するだけでなく、コレクションを集計に渡す方法がわかりませんでした。

IEnumerable<string> finalResults = null;
if (partitionKey.Length == 0) return finalResults;
object lockObject = new object();
finalResults = TableQueryGetRowKeys(partitionKey[0], 0);
HashSet<string> rowKeys;
for(int i = 1; i < partitionKey.Length; i++)
{
    // IO operation to Azure Table Storage against the PartitionKey
    // so very amenable to parallel
    rowKeys = TableQueryGetRowKeys(partitionKey[i]);
    // a memory and CPU operation 
    // this should be much faster than TableQueryGetRowKeys
    // going parallel and wrapping this in a lock did not properly synch rowKeys
    finalResults = finalResults.Intersect(rowKeys); 
}
return finalResults;
4

1 に答える 1

2

TableQueryGetRowKeysそれがスレッドセーフであると仮定します:

var final = partitionKey.AsParallel()
                        // By returning AsParallel we can get parallel intersect
                        .Select(k => TableQueryGetRowKeys(k).AsParallel())
                        .Aggregate((x, y) => x.Intersect(y));

// Using fake-ish data I see about a 30% speed-up on a 4-core machine:
// static HashSet<string> TableQueryGetRowKeys(string prefix)
// {
//     // Simulate 1s of IO round-trip
//     if (useSleep) Thread.Sleep(1000);
//
//     return new HashSet<string>(
//         Enumerable.Range(0, 500)
//                   .Select(_ => random.Value.Next(0, 500).ToString()));
// }

段階的な方法では、このアルゴリズムは次のように機能します。

  1. partitionKey.AsParallel()レギュラーをシーケンスの並列処理を可能IEnumerable<string>にする に変換します。ParallelQuery<string>
  2. 次に、並列ParallelEnumerable.Select呼び出しに使用されます。TableQueryGetRowKeys
  3. への各呼び出しの結果は、 usingTableQueryGetRowKeysでラップされます。ParallelQuery<T>AsParallel()
  4. ParallelEnumerable.Intersectによって返される各「並列対応」列挙に対する集計関数として使用されTableQueryGetRowKeysます。

実際には、これをシリアルに使用してAsParallel、次のように呼び出しを削除することで以前のコードを置き換えることができます。

var serialEquivalent = partitionKey.Select(k => TableQueryGetRowKeys(k))
                                   .Aggregate((x,y) => x.Intersect(y));

実装の肉とジャガイモを見ると、これがメソッドと同等であることを「確信」できます。

IEnumerable<string> results = SomeMethod(0);
for (int ii = 1; ii < count; ++ii)
{
    results = results.Intersect(SomeMethod(ii));
}

+の代わりに使用して上記を書き換えIntersectます。

int results = SomeMethod(0);
for (int ii = 1; ii < count; ++ii)
{
    results = results + SomeMethod(ii);
}

Intersectこれで、他のより「一般的な」集計関数 (数学演算子など) の代わりに使用できることが明らかになりました。

于 2012-07-06T17:04:52.820 に答える