2014年4月14日

Dictionary.TryGetValue のすゝめ

※ Dictionary について熟知している人は読み飛ばし推奨

Dictionary.TryGetValue というメソッドがある。
初見だと「何のためにあるの?」と疑問を抱く人は多い・・・はず。
処理内容が インデクサ(Item プロパティ) とかぶっているため、使う必要性を感じられずに無視してる人もいると思う。

結論から言うと、このメソッドは key の存在を確認してから value を取り出すパターンにおいて、非常に有効である。
  • ContainsKey + インデクサ

var dic = new Dictionary<string, int>();

// ---- key 追加処理 ----

if( dic.ContainsKey("ススメーススメー") ){
    Console.WriteLine(dic["ススメーススメー"]);
}
  • TryGetValue

var dic = new Dictionary<string, int>();

// ---- key 追加処理 ----

int val;
if( dic.TryGetValue("ススメーススメー", out val) ){
    Console.WriteLine(val);
}

上記の差は、TryGetValue 使用時の方が、key ルックアップが 1 回少なくなる点である。
key ルックアップはあまり早い処理ではないため、処理回数は少ないほどいい。

2014/09/26 修正:
Dictionary のソースを見て MSDN を読み間違えてたことに気付きました。
読み取り処理だけならスレッドセーフ、変更処理を含めるとスレッドセーフではない。

パフォーマンス

[測定コード]

static void Main(string[] args)
{
    // データの準備
    var dic = new Dictionary<string, int>();
    var key = "";
    for( var i=0; i<10; i++ ){
        key += "ススメー";
        dic.Add(key, i + 1);
    }
    int loopCount = 10000;
    var temp = new List<int>(loopCount);
    var flag = int.Parse(args[0]);
    var searchKey = args[1];

    // 測定実施
    var sw = new Stopwatch();
    sw.Start();
    switch( flag ){
    case 1:
        // ContainsKey + インデクサ
        for( var i=0; i<loopCount; i++ ){
            if( dic.ContainsKey(searchKey) ) temp.Add(dic[searchKey]);
        }
        break;
    case 2:
        // TryGetValue
        int ret;
        for( var i=0; i<loopCount; i++ ){
            if( dic.TryGetValue(searchKey, out ret) ) temp.Add(ret);
        }
        break;
    }
    sw.Stop();
    Console.WriteLine(sw.Elapsed);
}

[結果]
searchKey ContainsKey
+ インデクサ
TryGetValue
存在するキー ススメーススメー 0.0011107 0.0006471
存在しないキー ススメ 0.0002466 0.0002814
※コマンドプロンプトで実施(csc /optimize)
※10回の算術平均、単位は[s]

存在するキーの場合は、ルックアップが 1 回少ない TryGetValue の圧勝。
存在しないキーの場合は、ContainsKey と TryGetValue の速度差になる。

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0
Intel(R) Celeron(R) CPU G530 @ 2.40GHz/DDR3 8GB(4GB x 2)

参考URL

0 件のコメント:

コメントを投稿