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 は、残りのクエリに対し、順序を強制する演算子によって生成されるシーケンスの順序を維持することに注意してください。 つまり、OrderBy や ThenBy の演算子に続いて 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 ではインデックスが増加する順に出力要素が出力されることは保証しますが、どのインデックスがどの要素に割り当てられるかについては一切保証しません。
関連項目
.NET