none
DictionaryクラスでForEachメソッド

    質問

  • 先日、foreachで繰り返すより、Array.ForEach<T>List<T>.ForEach メソッドのほうが高速であるというのを見つけた のですが、これらよりも頻繁に利用するDictionaryクラスに、このようなメソッドないことに気付きました。

    取得したいのはValuesだけなので、

    Sample[] samples = new Sample[testData.Count];
    dictionary.Values.CopyTo(samples, 0);
    Array.ForEach<Sample>(samples, (sample) =>
    {
    	 // ここに処理
    });
    とValueCollections.CopyToメソッドを使って無理やり配列に入れなおしてからForEachを使ってみましたが、やはり配列のコピーに時間がかかるのか、処理時間はforeachと比べてあまり変化ないか、どちらかというと遅くなってしまいました

     

    どうしたもんかと、(いいのかどうか微妙ですが)Red Gate's .NET ReflectorでDictionaryクラスの実装をみたところ、ValueCollection.CopyToメソッドで

    int count = this.dictionary.count;
    Dictionary<TKey, TValue>.Entry[] entries = this.dictionary.entries;
    for (int i = 0; i < count; i++)
    {
    	if (entries[i].hashCode >= 0)
    	{
    		array[index++] = entries[i].value;
    	}
    }
    という処理をしているの見つけました。どうやら、entriesに直接アクセスできれば、ForEachメソッドを自分で実装できそうです。

     

    以前、InvokeMemberを使って無理やりprivateフィールドにアクセスしたことはありますが、これは常用するには少々危険な気がしますし、ほかに何かいい方法はないでしょうか?

    2010年4月7日 0:29

回答

  • 念のため確認ですが、目的はパフォーマンスなのですよね?
    だったら何も考えずに素直に組む方がいいです。

    単純なListなんかで単純な繰り返しであればForEachメソッドのようなやり方が高速にはなりますが、差は微々たるものです。
    記事では繰り返しないで足し算1回だけですが、普通はもっといろんな処理が入りますから、一般的な用途ではほとんど差は出ません。
    また、記事で比較されているのはあくまで反復子を使った場合との比較です。反復子での実装はオーバーヘッドが比較的大きくなりますが、既存のコレクションの組み込みのEnumeratorはわりと高速に実装されていたと思います。

    例えハッシュの内部エントリに直接アクセスしたとしても、内部はハッシュテーブルで実装されていますから、単に繰り返しで取り出すわけにはいきません(条件分岐が出てくるはずです)。
    当然オーバーヘッド部分が多くなり、差は小さくなります(差がなくなるかもしれない)。

    こんな微々たる効果のために、無理やりリフレクションで内部構造に依存するようなコードを書くメリットはありませんし、リフレクションを使わないなら効果があるような実装は多分できません(うまい方法はありません)。
    Dictionaryの更新が限られたタイミング(頻度が低いなど)で、要素数が少ないなら、Valuesのコピーをキャッシュしておく手はあるかもしれませんが。

    その場合でも、速度を求めるならForEachではなくてforeachか単なるforです。
    ちなみに記事では配列の場合もForEachメソッドを作成した方が速いと読み取れるような書き方になっているようですが、そんなことはないはずです(配列のforeachはほぼ最速です)。
    そもそもForEachを使うということはデリゲート経由の呼び出しになりますから、配列だと確実に遅くなります。

    • 回答としてマーク 青子守歌 2010年4月7日 1:57
    2010年4月7日 0:57

すべての返信

  • 念のため確認ですが、目的はパフォーマンスなのですよね?
    だったら何も考えずに素直に組む方がいいです。

    単純なListなんかで単純な繰り返しであればForEachメソッドのようなやり方が高速にはなりますが、差は微々たるものです。
    記事では繰り返しないで足し算1回だけですが、普通はもっといろんな処理が入りますから、一般的な用途ではほとんど差は出ません。
    また、記事で比較されているのはあくまで反復子を使った場合との比較です。反復子での実装はオーバーヘッドが比較的大きくなりますが、既存のコレクションの組み込みのEnumeratorはわりと高速に実装されていたと思います。

    例えハッシュの内部エントリに直接アクセスしたとしても、内部はハッシュテーブルで実装されていますから、単に繰り返しで取り出すわけにはいきません(条件分岐が出てくるはずです)。
    当然オーバーヘッド部分が多くなり、差は小さくなります(差がなくなるかもしれない)。

    こんな微々たる効果のために、無理やりリフレクションで内部構造に依存するようなコードを書くメリットはありませんし、リフレクションを使わないなら効果があるような実装は多分できません(うまい方法はありません)。
    Dictionaryの更新が限られたタイミング(頻度が低いなど)で、要素数が少ないなら、Valuesのコピーをキャッシュしておく手はあるかもしれませんが。

    その場合でも、速度を求めるならForEachではなくてforeachか単なるforです。
    ちなみに記事では配列の場合もForEachメソッドを作成した方が速いと読み取れるような書き方になっているようですが、そんなことはないはずです(配列のforeachはほぼ最速です)。
    そもそもForEachを使うということはデリゲート経由の呼び出しになりますから、配列だと確実に遅くなります。

    • 回答としてマーク 青子守歌 2010年4月7日 1:57
    2010年4月7日 0:57
  • 記事には「反復子」とあります。これはforeachのことではなくてyieldのことです。

    ですのでforeachよりForEach()メソッドの方が速いという解釈が間違っています。

    2010年4月7日 1:41
  • 記事では繰り返しないで足し算1回だけですが、普通はもっといろんな処理が入りますから、一般的な用途ではほとんど差は出ません。

    なるほど、この観点が抜けていました。

    確かに内部の処理を増やして(重く)したら、差が縮まるどころか、foreachで繰り返したほうが速くなりました。

     

    素直にforeachを使うことにします。ありがとうございました。

    2010年4月7日 2:05