Microsoft並列コンピュータチームのプログラムマネージャであるEd Essey氏は、まもなくリリースされる.NET 4.0ベータ1で登場する予定の最新版のPLINQの変更点について言及している。それによると、PLINQには"With"演算子パターン、実行モード、キャンセル、リファクタリング、性能向上などが盛り込まれる。
PLINQのベータ1の変更点の完全な一覧は次の通り。
- With演算子パターン
- 実行モード
- キャンセル
- カスタムパーティショニング
- リファクタリング
- マージオプション
- AsMergedをAsSequentialに変更
- 二項演算子の両辺にAsParallelが新たに必要に
- 性能改善
- めったに使われない演算子を廃止
“With” 演算子パターン: 新たに次の4メソッドが導入された。
- e.AsParallel().WithDegreeOfParallelism
- e.AsParallel().WithExecutionMode
- e.AsParallel().WithCancellation
- e.AsParallel().WithMergeOptions
実行モード:PLINQはLINQ-to-Objectsクエリと同様のリソースを消費するように調整されている。特にメモリ消費については。PLINQの呼び出しが多くのリソースを消費すると見なすと、その呼び出しは並列ではなくシーケンシャルに実行される。シーケンシャルな実行へと切り替える判断はクエリーの形に基づいてなされる。次のクエリーではシーケンシャルに実行される。
- インデックス付きのSelect、インデックス付きのWhere、インデックス付きのSelectManyを含むクエリや、元の順番ではもはやないインデックスでのElementAt。インデックスによる順番は、順序の変更をする演算子(OrderByなど)や要素を除去する演算子(Whereなど)に敏感である。
- Take、TakeWhile、Skip、SkipWhile演算子を含むクエリのうち、インデックスが元の順番ではないもの(上の項目を参照)。
- Zip、SequenceEqualsを含むクエリのうち、1つのデータソースのインデックスが元の順番ではなく、しかもその他のデータソースがインデックス可能でないもの(要するに配列やIList<T>のこと)。
- Concatを含むクエリのうち、それがインデックス可能なデータソースに適用されていないもの。
- Reverseを含むクエリのうち、それがインデックス可能なデータソースに適用されていないもの。
強制的に並列実行させたいときは次のようにすればよい。
e.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism)
キャンセル:並列操作は次のようにキャンセルすることができる。
var cts = new CancellationTokenSource();
var q = a.AsParallel().WithCancellation(cts.Token).Where(x=>Filter(x)).Select(x=>DoWork(x);
-- 別のスレッド --
foreach (var e in q) { … } // 状態(1)
-- 別のスレッド --
var l = q.ToList(); // 状態(2)
-- 別のスレッド --
cts.Cancel(); // 状態(1)(2)を含むすべてのしかかり中のクエリもキャンセルしようとする
カスタムパーティショニング:Partitioner<TSource>、OrderablePartitioner<TSource>クラスとそのファクトリクラスPartitionerはデータの分割の仕方をコントロールできるようにする。
リファクタリング: インターフェースIParallelEnumerable、IParallelEnumerable<T>およびIParallelOrderedEnumerable<T> は、インターフェースから継承できない抽象クラスになった。これらはそもそも継承されることを意図しないものだからだ。
マージオプション:「ParallelMergeOptionsの処理はAsMergedに移動した。マージバッファリングの指定はWithMergeOptionsメソッドで行うようになった」
AsMerged:AsMergedは見慣れたAsParallelと同じようにAsSequentialと改名された。
二項演算子:2つのデータソースを持つLINQ演算子は双方にAsParallelが必要になった。
a.AsParallel().AsOrdered().Zip(b, (x, y) => x*y);
を並列化するには
a.AsParallel().AsOrdered().Zip(b.AsParallel(), (x, y) => x*y);
とするか、
a.AsParallel().AsOrdered().Zip(b.AsParallel().AsOrdered(), (x, y) => x*y);
のようにする。影響する演算子は、Zip、Join、GroupJoin、Concat、SequenceEqual、Union、Intersect、Except。
性能改善:
1. 順序を保持するパイプライン方式のマージ。以前はAsOrderedをクエリに当てて、それぞれの要素がyieldされる前に強制的にクエリ全体が実行されていた。これはもう最適化され、マージオプションがDefault(AutoBuffered)かNotBufferedで作られたときには、クエリからの要素はyieldされることができる。
2. IList<T>を実装していないデータソースの公平な分割の改善
3. IList<T>や配列へのいくつかのクエリの性能向上
4. パーティショニングのチャンクサイズのチューニング。パーティショニングのチャンクはIList<T>や配列(つまり、インデックス化できないデータソース)以外のデータソースへのクエリをパーティショニングするもっとも一般的な仕組みである。これからは、チャンクのサイズはアクセスされればされるほど大きくなる。これは、2つのケース、すなわち小さいデータセットへのクエリだがクエリの中で高価なデリゲートと大きなデータセットへのクエリだがクエリの中の高価ではないデリゲートの間のバランスをとるためである。
5. falseになりそうな共有ケースを除去した。これによっていくつかのケースで6倍の改善になった。
めったに使われない演算子を廃止:性能上の理由で作られた演算子のうち、LINQ上で性能に寄与しなかったいくつかの演算子が廃止になった。どの演算子が廃止になったかは明記されなかった。