最近、C#でWindowsフォームアプリケーションを作成することが増えました。ループステートメントは基本的には適材適所の使い分けかと思いますが、私は主にforeach文を使用していました(書き方がシンプルで好きです)。ループステートメントは色々書き方あるものの速度は実際どうなのかなと気になったので、検証結果をまとめてみました。
ループステートメントの種類
for
for (初期化式; 条件式; 更新式) {
反復処理;
}
foreach
foreach (変数名 in 配列) {
反復処理;
}
while
while (条件式) {
反復処理;
}
do-while
do {
反復処理;
} while (条件式);
LINQ
書き方はソースで確認
検証ソース
1億個の要素を持った配列の総和を求める関数を100回実施した平均値を計測する。
using System;
using System.Linq;
namespace Verify
{
class Program
{
private static DateTime start;
private static DateTime end;
private static readonly int listCnt = 100000000;
private static readonly int loopCnt = 100;
private static readonly String resultFormat = "{0:0.0 ms}";
/// <summary>
/// 検証
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
LoopVerify();
}
/// <summary>
/// ループ系関数の速度検証
/// 1. for文
/// 2. foreach文
/// 3. while文
/// 4. do-while文
/// 5. LINQ文
/// 6. LINQ文(パターン2)
/// 予め用意した配列の要素トータルを計算する速度を検証する。
/// </summary>
private static void LoopVerify()
{
double[] doForVerifyList = new double[loopCnt];
double[] doForEachVerifyList = new double[loopCnt];
double[] doWhileVerifyList = new double[loopCnt];
double[] doDoWhileVerifyList = new double[loopCnt];
double[] doLINQQueryVerifyList = new double[loopCnt];
double[] doLINQMethodVerifyList = new double[loopCnt];
int[] verifyList = Enumerable.Repeat<int>(1, listCnt).ToArray();
for(int i = 0; i < loopCnt; i++)
{
doForVerifyList[i] = DoForVerify(verifyList);
doForEachVerifyList[i] = DoForEachVerify(verifyList);
doWhileVerifyList[i] = DoWhileVerify(verifyList);
doDoWhileVerifyList[i] = DoDoWhileVerify(verifyList);
doLINQQueryVerifyList[i] = DoLINQQueryVerify(verifyList);
doLINQMethodVerifyList[i] = DoLINQMethodVerify(verifyList);
}
// それぞれの平均値を出力
Console.WriteLine("ForAvg:" + String.Format(resultFormat, doForVerifyList.Average()));
Console.WriteLine("ForEachAvg:" + String.Format(resultFormat, doForEachVerifyList.Average()));
Console.WriteLine("WhileAvg:" + String.Format(resultFormat, doWhileVerifyList.Average()));
Console.WriteLine("DoWhileAvg:" + String.Format(resultFormat, doDoWhileVerifyList.Average()));
Console.WriteLine("LINQQueryAvg:" + String.Format(resultFormat, doLINQQueryVerifyList.Average()));
Console.WriteLine("LINQMethodAvg:" + String.Format(resultFormat, doLINQMethodVerifyList.Average()));
}
/// <summary>
/// forの検証
/// </summary>
/// <param name="_verifyList">検証配列</param>
/// <returns>経過時間(ミリ秒)</returns>
private static double DoForVerify(int[] _verifyList)
{
int total = 0;
start = DateTime.Now;
for (int i = 0; i <= _verifyList.Length - 1; i++)
{
total += _verifyList[i];
}
end = DateTime.Now;
return (end - start).TotalMilliseconds;
}
/// <summary>
/// foreachの検証
/// </summary>
/// <param name="_verifyList">検証配列</param>
/// <returns>経過時間(ミリ秒)</returns>
private static double DoForEachVerify(int[] _verifyList)
{
int total = 0;
start = DateTime.Now;
foreach (int i in _verifyList)
{
total += i;
}
end = DateTime.Now;
return (end - start).TotalMilliseconds;
}
/// <summary>
/// whileの検証
/// </summary>
/// <param name="_verifyList">検証配列</param>
/// <returns>経過時間(ミリ秒)</returns>
private static double DoWhileVerify(int[] _verifyList)
{
int total = 0;
int i = 0;
start = DateTime.Now;
while (i <= _verifyList.Length - 1)
{
total += _verifyList[i];
i++;
}
end = DateTime.Now;
return (end - start).TotalMilliseconds;
}
/// <summary>
/// do-whileの検証
/// </summary>
/// <param name="_verifyList">検証配列</param>
/// <returns>経過時間(ミリ秒)</returns>
private static double DoDoWhileVerify(int[] _verifyList)
{
int total = 0;
int i = 0;
start = DateTime.Now;
do
{
total += _verifyList[i];
i++;
}
while (i <= _verifyList.Length - 1);
end = DateTime.Now;
return (end - start).TotalMilliseconds;
}
/// <summary>
/// LINQの検証(クエリ構文)
/// </summary>
/// <param name="_verifyList">検証配列</param>
/// <returns>経過時間(ミリ秒)</returns>
private static double DoLINQQueryVerify(int[] _verifyList)
{
start = DateTime.Now;
int total = (
from x in _verifyList
select x
).Sum();
end = DateTime.Now;
return (end - start).TotalMilliseconds;
}
/// <summary>
/// LINQの検証(メソッド構文)
/// </summary>
/// <param name="_verifyList">検証配列</param>
/// <returns>経過時間(ミリ秒)</returns>
private static double DoLINQMethodVerify(int[] _verifyList)
{
start = DateTime.Now;
int total = _verifyList
.Sum(x => x);
end = DateTime.Now;
return (end - start).TotalMilliseconds;
}
}
}
実行結果
1回目
ForAvg:387.8 ms
ForEachAvg:419.6 ms
WhileAvg:391.0 ms
DoWhileAvg:391.2 ms
LINQQueryAvg:1236.2 ms
LINQMethodAvg:1216.8 ms
2回目
ForAvg:375.6 ms
ForEachAvg:408.2 ms
WhileAvg:377.1 ms
DoWhileAvg:366.7 ms
LINQQueryAvg:1188.7 ms
LINQMethodAvg:1176.5 ms
3回目
ForAvg:354.1 ms
ForEachAvg:379.8 ms
WhileAvg:358.9 ms
DoWhileAvg:355.1 ms
LINQQueryAvg:1135.5 ms
LINQMethodAvg:1124.3 ms
結論
for文が1番早かったです。LINQは抜きにしても、foreachが一番遅かったのは少々ショックな結果となりました。
今回、反復処理の中で特に条件分岐等がなかったためLINQには不利な検証条件だったかと思います。
そのため、もう一度それらを踏まえた検証を行いたいと思います。次回に乞うご期待を。