商品コードと値段 のレコードに対し、値段の合計を求めるコードを考えてみたいと思います。

Dictionary 対 List

Dictionary がハッシュテーブルなのに対し、List は順序配列なので、
今回の処理では当然 Listの方が速いと思われます。

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        // テストデータ作成
        var dic = new Dictionary();
        var list = new List<KeyValuePair>();
        for (int i = 0; i < 10000000; i++)
        {
            dic.Add(i, i % 7);
            list.Add(new KeyValuePair(i, i % 7));
        }

        var sw = new System.Diagnostics.Stopwatch();

        sw.Start();
        int total = 0;
        foreach (var key in dic.Keys)
        {
            total += dic[key];
        }
        sw.Stop();
        Console.WriteLine("Dictionary: {0}ms", sw.ElapsedMilliseconds);

        sw.Restart();
        total = 0;
        foreach (var item in list)
        {
            total += item.Value;
        }
        sw.Stop();
        Console.WriteLine("List: {0}ms", sw.ElapsedMilliseconds);
    }
}

実行結果:

Dictionary: 214ms  
List: 88ms

予想通りです。

より速く

Dictionary のループの仕方を変えてやることで、速度改善が図れることがあります。

sw.Restart();
total = 0;
foreach (var item in dic)
{
    total += item.Value;
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

実行結果:

156ms

Dictionary.Keys ではなく、Dictionary のアイテムそのものを回してやることで、
3割近く改善することができました。

さらに、Valuesそのものを回してやると、

sw.Restart();
total = 0;
foreach (var v in dic.Values)
{
    total += v;
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

実行結果:

63ms

誤差の範囲かも知れませんが、List より速くなりました。

他の方法

せっかくなので、他の方法も試してみます。

sw.Restart();
total = dic.Sum(r => r.Value);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

実行結果:

235ms

Dictionary.Keys のループとあまり変わりませんが、意味はわかりやすいし、
シンプルだし、速度を求めないのであれば個人的には好みです。

無理やりですが配列っぽくアクセスする方法も試してみます。

sw.Restart();
total = 0;
for (int i = 0; i < dic.Count; i++)
{
    total += dic.ElementAt(i).Value;
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

ただし、この方法は非常に遅いです。
私のPCではテストデータを 1/1000 の個数に変更してようやく、327ms でした。

まとめ(私見)

  • Keyを使ったアクセスが重要でないなら List を使うべき。
  • Dictionary をループさせるなら、Keys ではなく Dictionaryのアイテムで回す。
  • 速度重視でないなら、Linq がわかりやすい。
  • ElementAt() はループの中では使わない。
TOP
TOP