PLINQ における順序維持

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

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

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

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

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

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

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

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

        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

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 { Name = city.Name, Pop = city.Population, Mayor = c.Mayor };

foreach (var city in finalResult) { /*...*/ }

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

クエリ演算子と順序付け

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

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

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

演算子

ソース シーケンスが順序ありの場合の結果

ソース シーケンスが順序なしの場合の結果

Aggregate

非結合演算子または非可換演算子の場合は非確定の出力

非結合演算子または非可換演算子の場合は非確定の出力

All<TSource>

該当なし

該当なし

Any

該当なし

該当なし

AsEnumerable<TSource>

該当なし

該当なし

Average

非結合演算子または非可換演算子の場合は非確定の出力

非結合演算子または非可換演算子の場合は非確定の出力

Cast<TResult>

順序ありの結果

順序なしの結果

Concat

順序ありの結果

順序なしの結果

Count

該当なし

該当なし

DefaultIfEmpty

該当なし

該当なし

Distinct

順序ありの結果

順序なしの結果

ElementAt<TSource>

指定された要素を返す

任意の要素

ElementAtOrDefault<TSource>

指定された要素を返す

任意の要素

Except

順序なしの結果

順序なしの結果

First

指定された要素を返す

任意の要素

FirstOrDefault

指定された要素を返す

任意の要素

ForAll<TSource>

非確定的に並列実行

非確定的に並列実行

GroupBy

順序ありの結果

順序なしの結果

GroupJoin

順序ありの結果

順序なしの結果

Intersect

順序ありの結果

順序なしの結果

Join

順序ありの結果

順序なしの結果

Last

指定された要素を返す

任意の要素

LastOrDefault

指定された要素を返す

任意の要素

LongCount

該当なし

該当なし

Min

該当なし

該当なし

OrderBy

シーケンスを並べ替え

新規に順序付けられたセクションを開始

OrderByDescending

シーケンスを並べ替え

新規に順序付けられたセクションを開始

Range

該当なし (AsParallel の既定と同じ)

該当なし

Repeat<TResult>

該当なし (AsParallel の既定と同じ)

該当なし

Reverse<TSource>

逆方向

処理を行わない

Select

順序ありの結果

順序なしの結果

Select (インデックス付き)

順序ありの結果

順序なしの結果

SelectMany

順序ありの結果

順序なしの結果

SelectMany (インデックス付き)

順序ありの結果

順序なしの結果

SequenceEqual

順序ありの比較

順序なしの比較

Single

該当なし

該当なし

SingleOrDefault

該当なし

該当なし

Skip<TSource>

最初の n 要素をスキップ

n 要素をスキップ

SkipWhile

順序ありの結果

非確定。 現在の任意の順序で SkipWhile を実行

Sum

非結合演算子または非可換演算子の場合は非確定の出力

非結合演算子または非可換演算子の場合は非確定の出力

Take<TSource>

最初の n 要素を取得

n 要素を取得

TakeWhile

順序ありの結果

非確定。 現在の任意の順序で TakeWhile を実行

ThenBy

OrderBy を補足

OrderBy を補足

ThenByDescending

OrderBy を補足

OrderBy を補足

ToTSource[]

順序ありの結果

順序なしの結果

ToDictionary

該当なし

該当なし

ToList<TSource>

順序ありの結果

順序なしの結果

ToLookup

順序ありの結果

順序なしの結果

Union

順序ありの結果

順序なしの結果

Where

順序ありの結果

順序なしの結果

Where (インデックス付き)

順序ありの結果

順序なしの結果

Zip

順序ありの結果

順序なしの結果

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

参照

概念

Parallel LINQ (PLINQ)

.NET Framework の並列プログラミング