C#やVBは今月(2025年2月)から触り始めたので、
変なところがあったらすみません。ご指摘等いただけると幸いです。
C#(?)でリストの順番を意識しつつ並列化
現場で.net系を使うことになった際、
「リストを並列処理⇒処理結果を元のリストと同じ順番でリスト化する方法」
を学んだので、備忘として残しておきます。
やりたいこと
リストをfor文などでループし別リストを作成する処理を並列化(パフォーマンス改善)
やったこと
・修正前コード例
using System.ComponentModel;
var listA = new List<int>();
// 1万件のリストを用意(実際は10万件くらい)
for (int i = 0; i < 10000; i++)
{
listA.Add(i);
}
var startDate = DateTime.Now;
var listB = new List<int>();
for (int i = 0; i < listA.Count; i++)
{
listB.Add(listA[i]);
}
var endDate = DateTime.Now;
Console.WriteLine("処理時間:" + (endDate - startDate).TotalMilliseconds + "ms");
・修正後コード例
(略)
// ポイント1:listAと同じだけのsizeを持つ、格納先listを作成
var listB = new List<int>(new int[listA.Count]);
var startDate = DateTime.Now;
// ポイント2:並列化し、indexに対応するところにデータを入れる
Parallel.ForEach(listA, (data, state, index) =>
{
listB[Convert.ToInt32(index)] = listA[Convert.ToInt32(index)];
});
var endDate = DateTime.Now;
Console.WriteLine("処理時間:" + (endDate - startDate).TotalMilliseconds + "ms");
以下コメントで頂いたもの
・1行で記載する方法
(略)
var startDate = DateTime.Now;
//お手軽な書き方(実質一行)
var listB = listA.AsParallel()
.AsOrdered() //順序を維持するよう指示
//.Select(data => data * 2) //何か処理をした結果を渡したい場合はここに処理を書く
.ToList();
var endDate = DateTime.Now;
Console.WriteLine("処理時間:" + (endDate - startDate).TotalMilliseconds + "ms");
・Zipで記載する方法
(略)
List<int> listB = [.. new int[listA.Count]];
var startDate = DateTime.Now;
listA.Zip(listA.Select((_, i) => i)).AsParallel().ForAll(data => listB[data.Second] = data.First);
var endDate = DateTime.Now;
Console.WriteLine("処理時間:" + (endDate - startDate).TotalMilliseconds + "ms");
・AsOrderedで記載する方法
(略)
var startDate = DateTime.Now;
List<int> listB = [];
// AsOrdered()を使い、順序を保持
listA.AsParallel().AsOrdered().ForAll(listB.Add);
var endDate = DateTime.Now;
Console.WriteLine("処理時間:" + (endDate - startDate).TotalMilliseconds + "ms");
それぞれ実際に動かしてみた
<修正前>
1回目:5.1387ms
2回目:6.7196ms
3回目:4.7355ms
<修正後>
1回目:14.5718ms
2回目:14.2512ms
3回目:15.6844ms
<1行>
1回目:16.1567ms
2回目:15.0027ms
3回目:14.1936ms
<Zip>
1回目:20.1356ms
2回目:18.8725ms
3回目:20.6022ms
<AsOrdered>
1回目:12.4384ms
2回目:13.1296ms
3回目:13.9489ms
【補足】
ちなみに、修正後の方が遅くなったので、
試しに修正前後のループ箇所に "Thred.sleep(5)" を入れて実行してみた。
修正前:154720.268ms
修正後:9000.7618ms
まとめ
並列化すれば絶対に早くなるわけでもないらしい。
並列化したら早くなるかは、実際に試して確認すべきみたいです。
(現場は1/3~1/4位の処理時間にはなった)