PLINQ における順序維持

PLINQ では、正確性を維持しながらパフォーマンスを最大にすることが重要です。 クエリをできるだけ速く実行する一方で、正確な結果を生成する必要があります。 正確性のために、ソース シーケンスの順序の維持が必要な場合がありますが、順序付けには負荷がかかります。 したがって、既定では、PLINQ はソース シーケンスの順序を維持しません。 この点で、PLINQ は LINQ to SQL と似ていますが、順序を維持する LINQ to Objects とは異なります。

既定の動作をオーバーライドするには、ソース シーケンス上で AsOrdered 演算子を使用して、順序の維持を有効にします。 その後、AsUnordered メソッドを使用して、クエリでの順序の維持を無効にできます。 どちらの方法でも、クエリを並列実行するか順次実行するかを決定するヒューリスティックに基づいてクエリが処理されます。 詳細については、「Understanding Speedup in PLINQ (PLINQ での高速化について)」を参照してください。

次の例では、結果を順序付けず、条件に一致するすべての要素をフィルター処理する、順序なしの並列クエリを示しています。

var cityQuery =
    (from city in cities.AsParallel()
     where city.Population > 10000
     select city).Take(1000);
Dim cityQuery = From city In cities.AsParallel()
                Where city.Population > 10000
                Take (1000)

このクエリでは、ソース シーケンスで条件を満たす最初の 1000 都市が生成されるとは限らず、条件を満たす一連の 1000 都市が生成されます。 PLINQ クエリ演算子は、同時実行タスクとして処理される複数のサブシーケンスにソース シーケンスをパーティション分割します。 順序の維持が指定されていない場合、パーティションごとの結果はクエリの次のステージに任意の順序で渡されます。 また、パーティションでは、残りの要素の処理が続行される前に、結果のサブセットが生成される場合があります。 結果の順序は毎回異なることがあります。 この動作は、オペレーティング システムによるスレッドのスケジュール方法に依存するため、アプリケーションでは制御できません。

次の例では、ソース シーケンス上で AsOrdered 演算子を使用して、既定の動作をオーバーライドしています。 この例では Take メソッドにより、ソース シーケンスで条件を満たす最初の 1000 都市が返されます。

var orderedCities =
    (from city in cities.AsParallel().AsOrdered()
     where city.Population > 10000
     select city).Take(1000);

Dim orderedCities = From city In cities.AsParallel().AsOrdered()
                    Where city.Population > 10000
                    Take (1000)

ただし、このクエリは順序なしのクエリと同じ速度では実行されません。パーティション全体とマージ時刻で元の順序を追跡し、順序が一貫していることを確認する必要があるためです。 したがって、AsOrdered は、必要な場合にのみ、クエリの該当部分に限って使用することをお勧めします。 順序を維持する必要がなくなったら、AsUnordered を使用して無効にします。 2 つのクエリを組み合わせてこの処理を行う例を次に示します。

var orderedCities2 =
    (from city in cities.AsParallel().AsOrdered()
     where city.Population > 10000
     select city).Take(1000);

var finalResult =
    from city in orderedCities2.AsUnordered()
    join p in people.AsParallel()
    on city.Name equals p.CityName into details
    from c in details
    select new
    {
        city.Name,
        Pop = city.Population,
        c.Mayor
    };

foreach (var city in finalResult) { /*...*/ }
Dim orderedCities2 = From city In cities.AsParallel().AsOrdered()
                     Where city.Population > 10000
                     Select city
                     Take (1000)

Dim finalResult = From city In orderedCities2.AsUnordered()
                  Join p In people.AsParallel() On city.Name Equals p.CityName
                  Select New With {.Name = city.Name, .Pop = city.Population, .Mayor = city.Mayor}

For Each city In finalResult
    Console.WriteLine(city.Name & ":" & city.Pop & ":" & city.Mayor)
Next

PLINQ は、残りのクエリに対し、順序を強制する演算子によって生成されるシーケンスの順序を維持することに注意してください。 つまり、OrderByThenBy の演算子に続いて AsOrdered が呼び出されるのと同じように処理されます。

クエリ演算子と順序付け

次のクエリ演算子は、クエリ内のすべての後続演算子で順序を維持するか、または AsUnordered が呼び出されるまで順序を維持します。

次の PLINQ クエリ演算子では、正確な結果を生成するために順序ありのソース シーケンスが必要となる場合があります。

PLINQ クエリ演算子の中には、ソース シーケンスが順序ありか順序なしかによって動作が異なるものがあります。 次の表に、これらの演算子の一覧を示します。

演算子 ソース シーケンスが順序ありの場合の結果 ソース シーケンスが順序なしの場合の結果
Aggregate 非結合演算子または非可換演算子の場合は非確定の出力 非結合演算子または非可換演算子の場合は非確定の出力
All 適用できません 適用なし
Any 適用なし 適用なし
AsEnumerable 適用なし 適用できません
Average 非結合演算子または非可換演算子の場合は非確定の出力 非結合演算子または非可換演算子の場合は非確定の出力
Cast 順序ありの結果 順序なしの結果
Concat 順序ありの結果 順序なしの結果
Count 適用できません 適用なし
DefaultIfEmpty 適用なし 適用できません
Distinct 順序ありの結果 順序なしの結果
ElementAt 指定された要素を返す 任意の要素
ElementAtOrDefault 指定された要素を返す 任意の要素
Except 順序なしの結果 順序なしの結果
First 指定された要素を返す 任意の要素
FirstOrDefault 指定された要素を返す 任意の要素
ForAll 非確定的に並列実行 非確定的に並列実行
GroupBy 順序ありの結果 順序なしの結果
GroupJoin 順序ありの結果 順序なしの結果
Intersect 順序ありの結果 順序なしの結果
Join 順序ありの結果 順序なしの結果
Last 指定された要素を返す 任意の要素
LastOrDefault 指定された要素を返す 任意の要素
LongCount 適用できません 適用なし
Min 適用なし 適用できません
OrderBy シーケンスを並べ替え 新規に順序付けられたセクションを開始
OrderByDescending シーケンスを並べ替え 新規に順序付けられたセクションを開始
Range 該当なし (AsParallel の既定と同じ) 適用できません
Repeat 該当なし (AsParallel の既定と同じ) 適用できません
Reverse 逆方向 処理を行わない
Select 順序ありの結果 順序なしの結果
Select (インデックス付き) 順序ありの結果 順序なしの結果
SelectMany 順序ありの結果 順序なしの結果
SelectMany (インデックス付き) 順序ありの結果 順序なしの結果
SequenceEqual 順序ありの比較 順序なしの比較
Single 適用できません 適用なし
SingleOrDefault 適用なし 適用できません
Skip 最初の n 要素をスキップ 任意の n 要素をスキップ
SkipWhile 順序ありの結果 非確定。 現在の任意の順序で SkipWhile を実行
Sum 非結合演算子または非可換演算子の場合は非確定の出力 非結合演算子または非可換演算子の場合は非確定の出力
Take 最初の n 要素を取得 n 要素を取得
TakeWhile 順序ありの結果 非確定。 現在の任意の順序で TakeWhile を実行
ThenBy OrderBy を補足 OrderBy を補足
ThenByDescending OrderBy を補足 OrderBy を補足
ToArray 順序ありの結果 順序なしの結果
ToDictionary 適用できません 適用できません
ToList 順序ありの結果 順序なしの結果
ToLookup 順序ありの結果 順序なしの結果
Union 順序ありの結果 順序なしの結果
Where 順序ありの結果 順序なしの結果
Where (インデックス付き) 順序ありの結果 順序なしの結果
Zip 順序ありの結果 順序なしの結果

順序なしの結果はアクティブにシャッフルされるわけではありません。適用される特別な順序ロジックがないだけです。 順序なしのクエリでソース シーケンスの順序が保持される場合もあります。 インデックス付きの Select 演算子を使用するクエリの場合、PLINQ ではインデックスが増加する順に出力要素が出力されることは保証しますが、どのインデックスがどの要素に割り当てられるかについては一切保証しません。

関連項目