はじめに
日々C#で開発をしている中で、
「手動でインデックスをインクリメントするループ」と
「LINQのSelectでインデックスを取得するループ」
のどちらがより効率的か、ふと疑問に思う場面がありました。
今回、簡単な検証を通して
- メモリ使用量
-
実行時間
にどれくらい差が出るのかを比較してみたので、その結果をまとめます。
実験内容
使用したコード
検証のために、以下のようなコードを作成しました。
オブジェクト作成とループ検証プログラム
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
public class MyData {
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public MyData(int id, string name, int age)
{
Id = id;
Name = name;
Age = age;
}
}
class Program
{
public static string LOOPTYPE = "while"; // "for", "foreach", "foreachLinq", "while"
public static int LOOPNUMBER = 1000000;
static List<MyData> create(int numberOfObjects)
{
List<MyData> objects = new List<MyData>();
for (int i = 0; i < numberOfObjects; i++)
{
var data = new MyData(i + 1, "pokepoke", 12);
objects.Add(data);
}
Console.WriteLine($"作成したオブジェクト数: {objects.Count}");
return objects;
}
static void Main()
{
var sw = new Stopwatch();
List<MyData> sampleList = create(LOOPNUMBER);
sw.Start();
long initialMemory = GC.GetTotalMemory(false);
Console.WriteLine($"初期メモリ: {initialMemory:N0} bytes");
Console.WriteLine($"{LOOPTYPE}");
for (int i = 0; i < 10; i++)
{
int[] id = new int[sampleList.Count];
string[] name = new string[sampleList.Count];
int index = 0;
switch(LOOPTYPE){
case "for":
for (int j = 0; j < sampleList.Count; j++){
id[j] = sampleList[j].Id;
name[j] = sampleList[j].Name;
}
break;
case "foreach":
foreach(var value in sampleList){
id[index] = value.Id;
name[index] = value.Name;
index++;
}
break;
case "foreachLinq":
foreach(var value in sampleList.Select((item,index) => new {item,index})){
id[value.index] = value.item.Id;
name[value.index] = value.item.Name;
}
break;
case "while":
while (index < sampleList.Count){
id[index] = sampleList[index].Id;
name[index] = sampleList[index].Name;
index++;
}
break;
default:
break;
}
long currentMemory = GC.GetTotalMemory(false);
Console.WriteLine($"[{i}] メモリ: {currentMemory:N0} bytes 経過時間: {sw.ElapsedMilliseconds} ms");
System.Threading.Thread.Sleep(500);
}
sw.Stop();
Console.WriteLine($"総時間: {sw.ElapsedMilliseconds} ms");
}
}
コード要約
このコードは、
-
for
/foreach
/foreach+LINQ
/while
の4つのループ方式で、 - 100万件など大量のオブジェクトに対して、
- メモリ使用量と処理時間を比較する
ための簡易パフォーマンス検証ツールです。
実験条件
オブジェクト数
- 10,000件
- 100,000件
- 1,000,000件
計測対象
- メモリ使用量(
GC.GetTotalMemory(true)
使用) - 実行時間(
Stopwatch
使用)
実行環境
- ※ここに開発環境を書く予定(例:Windows 11 / .NET 8 / Releaseビルド etc.)
実験結果
オブジェクト数 | 方法 | メモリ増加量 | 実行時間 |
---|---|---|---|
10,000 | 手動インクリメント | 少ない | 速い |
10,000 | LINQ Select | やや多い | やや遅い |
100,000 | 手動インクリメント | 少ない | 速い |
100,000 | LINQ Select | 多め | 遅い |
1,000,000 | 手動インクリメント | 少ない | やや遅い(でもまだ速い) |
1,000,000 | LINQ Select | さらに多い | さらに遅い |
ざっくりまとめると
- オブジェクト数が多くなるほど差が開く
- 10,000件程度ならほぼ誤差レベル
考察(現場目線で)
今回の結果を現場で活かすなら、以下のような判断ができそうです。
条件 | 推奨方法 |
---|---|
少量データ(数千〜数万件) | どちらでもOK。可読性重視でSelectもアリ |
大量データ(数十万件以上) | 手動インクリメントを使ったほうが無難 |
理由
- LINQを使うと「一時オブジェクト」が内部で生成されるため、オーバーヘッドが無視できない
- また、LINQの内部イテレータ(
SelectIterator
)によりメモリ圧迫・GCコストも増える - 少量なら開発スピード優先、大量ならパフォーマンス優先がベター
まとめ
- LINQのインデックス取得は少量データなら気にせず使ってOK
- 大量データ処理では、素直な
for
やインクリメント管理のほうが安全 - 可読性 vs パフォーマンス、どちらを重視すべきか現場ごとに判断する
今後も「なんとなくLINQを使う」ではなく、場面に応じた選択を意識していきたいと思いました。
記事の内容で間違いとかあれば指摘していただきたいです
今後の勉強にやくだてさせていただきます