23

私たちのアプリケーションは、頻繁に変更されないマルチレベルのルックアップを持つ多くの辞書を使用しています。ディクショナリを使用して多くのルックアップを行う重要なコードの一部を、より高速なルックアップ、メモリ/gc の軽量化のために代替構造を使用するように変換することを調査しています。これにより、利用可能なさまざまな辞書/ライブラリを比較しました-

Dictionary( System.Collections.Generics.Dictionary-SCGD), ImmutableDictionary, C5.HashDictionary, FSharpMap.

さまざまな項目数 (100、1000、10000、100000) で次のプログラムを実行すると、Dictionary が依然としてほとんどの範囲で勝者であることがわかります。最初の行はコレクション内のアイテムを示します。MS/Ticks は、ランダム化された x ルックアップの実行にかかる時間になります (コードは以下にあります)。

項目 - 100
SCGD - 0 MS - 50 ティック
C5 - 1 MS - 1767 ティック
Imm - 4 MS - 5951 ティック
FS - 0 MS - 240 ティック

項目 - 1000
SCGD - 0 MS - 230 ティック
C5 - 0 MS - 496 ティック
Imm - 0 MS - 1046 ティック
FS - 1 MS - 1870 ティック

項目 - 10000
SCGD - 3 MS - 4722 ティック
C5 - 4 MS - 6370 ティック
Imm - 9 MS - 13119 ティック
FS - 15 MS - 22050 ティック

項目 - 100000
SCGD - 62 MS - 89276 ティック
C5 - 72 MS - 103658 ティック
Imm - 172 MS - 246247 ティック
FS - 249 MS - 356176 ティック

これは、すでに最速のものを使用しており、変更する必要がないということですか? 不変の構造はテーブルの一番上にあるはずだと思っていましたが、そうではありませんでした。私たちは間違った比較をしていますか、それとも何か不足していますか? この質問をしがみついていましたが、聞いたほうがいいと感じました。リンクやメモ、または何らかの光を投げかける参照は素晴らしいものです。

テスト用の完全なコード -

namespace CollectionsTest
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Immutable;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Runtime;
    using Microsoft.FSharp.Collections;

    /// <summary>
    /// 
    /// </summary>
    class Program
    {
        static Program()
        {
            ProfileOptimization.SetProfileRoot(@".\Jit");
            ProfileOptimization.StartProfile("Startup.Profile");
        }

        /// <summary>
        /// Mains the specified args.
        /// </summary>
        /// <param name="args">The args.</param>
        static void Main(string[] args)
        {
            // INIT TEST DATA ------------------------------------------------------------------------------------------------

            foreach (int MAXITEMS in new int[] { 100, 1000, 10000, 100000 })
            {
                Console.WriteLine("\n# - {0}", MAXITEMS);

                List<string> accessIndex = new List<string>(MAXITEMS);
                List<KeyValuePair<string, object>> listofkvps = new List<KeyValuePair<string, object>>();
                List<Tuple<string, object>> listoftuples = new List<Tuple<string, object>>();
                for (int i = 0; i < MAXITEMS; i++)
                {
                    listoftuples.Add(new Tuple<string, object>(i.ToString(), i));
                    listofkvps.Add(new KeyValuePair<string, object>(i.ToString(), i));
                    accessIndex.Add(i.ToString());
                }

                // Randomize for lookups
                Random r = new Random(Environment.TickCount);
                List<string> randomIndexesList = new List<string>(MAXITEMS);
                while (accessIndex.Count > 0)
                {
                    int index = r.Next(accessIndex.Count);
                    string value = accessIndex[index];
                    accessIndex.RemoveAt(index);

                    randomIndexesList.Add(value);
                }

                // Convert to array for best perf
                string[] randomIndexes = randomIndexesList.ToArray();

                // LOAD ------------------------------------------------------------------------------------------------

                // IMMU
                ImmutableDictionary<string, object> idictionary = ImmutableDictionary.Create<string, object>(listofkvps);
                //Console.WriteLine(idictionary.Count);

                // SCGD
                Dictionary<string, object> dictionary = new Dictionary<string, object>();
                for (int i = 0; i < MAXITEMS; i++)
                {
                    dictionary.Add(i.ToString(), i);
                }
                //Console.WriteLine(dictionary.Count);

                // C5
                C5.HashDictionary<string, object> c5dictionary = new C5.HashDictionary<string, object>();
                for (int i = 0; i < MAXITEMS; i++)
                {
                    c5dictionary.Add(i.ToString(), i);
                }
                //Console.WriteLine(c5dictionary.Count);
                // how to change to readonly?

                // F#
                FSharpMap<string, object> fdictionary = new FSharpMap<string, object>(listoftuples);
                //Console.WriteLine(fdictionary.Count);

                // TEST ------------------------------------------------------------------------------------------------
                Stopwatch sw = Stopwatch.StartNew();
                for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
                {
                    string i = randomIndexes[index];
                    object value;
                    dictionary.TryGetValue(i, out value);
                }
                sw.Stop();
                Console.WriteLine("SCGD - {0,10} MS - {1,10} Ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks);

                Stopwatch c5sw = Stopwatch.StartNew();
                for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
                {
                    string key = randomIndexes[index];
                    object value;
                    c5dictionary.Find(ref key, out value);
                }
                c5sw.Stop();
                Console.WriteLine("C5   - {0,10} MS - {1,10} Ticks", c5sw.ElapsedMilliseconds, c5sw.ElapsedTicks);

                Stopwatch isw = Stopwatch.StartNew();
                for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
                {
                    string i = randomIndexes[index];
                    object value;
                    idictionary.TryGetValue(i, out value);
                }
                isw.Stop();
                Console.WriteLine("Imm  - {0,10} MS - {1,10} Ticks", isw.ElapsedMilliseconds, isw.ElapsedTicks);


                Stopwatch fsw = Stopwatch.StartNew();
                for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
                {
                    string i = randomIndexes[index];
                    fdictionary.TryFind(i);
                }
                fsw.Stop();
                Console.WriteLine("FS   - {0,10} MS - {1,10} Ticks", fsw.ElapsedMilliseconds, fsw.ElapsedTicks);
            }
        }
    }
}
4

5 に答える 5

17

ほとんどすべての不変コレクションが「変更」時に構造全体をコピーすることを回避する方法は、データをツリーに保存し、「変更」時にノードの一部のみをコピーし、すべて他のノード。また、ツリーへのアクセスは、一般に、変更可能ないとこが行うように、インデックスによるフラット配列へのアクセスよりも遅くなります。

あなたのコードに基づいて、 、、およびのシングルスレッド読み取りパフォーマンスを比較しました。Dictionary<,>ConcurrentDictionary<,>ImmutableDictionary<,>

ウォームアップ後の 30 回の実行の平均結果:

さまざまなディクショナリ実装の読み取りパフォーマンス

書き込みパフォーマンスの感触をつかむために、辞書にさらに 50 のエントリを追加するテストも実行しました。繰り返しますが、ウォームアップ後の 30 回の実行の平均結果は次のとおりです。

さまざまなディクショナリ実装の書き込みパフォーマンス

テスト済み

  • .net 4.5.1
  • Microsoft Bcl 不変 1.0.34.0

NB不変辞書は非常に高速であり、多くの実際のマルチスレッド アプリケーションでより高いレベルの同時実行を可能にすることに注意してください。、スレッドに直面した可変性に対処するため。これは、オプティミスティック コンカレンシー、MVCC などのスナップショット機能が必要な場合に特に当てはまります。

ところで、サンプル コードをそのまま実行すると、少なくとも不変ディクショナリの値は、通常の (実行時間の長い) アプリケーションでは非常に一般的ではなくなります。パフォーマンスの違いは非常に大きいです。最初の 3 回の実行の出力を見てください。

Items    Dict   Conc   Immu
===========================
   100   1.90   1.00 361.81
  1000   1.07   1.00   4.33
 10000   1.24   1.00   1.74
100000   1.00   1.33   2.71
---------------------------
   100   1.06   1.00   2.56
  1000   1.03   1.00   4.34
 10000   1.00   1.06   3.54
100000   1.00   1.17   2.76
---------------------------
   100   1.06   1.00   2.50
  1000   1.66   1.00   4.16
 10000   1.00   1.02   3.67
100000   1.00   1.26   3.13

あなたの質問は(凍結された辞書の)読み取りパフォーマンスに関するものでしたが、不変コレクションのツリー特性は書き込みパフォーマンスでも同様に示されています。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                       Mutable (amortized)  Mutable (worst)  Immutable 
───────────────────────────────────────────────────────────────────────
 Stack.Push            O(1)                 O(n)             O(1)      
 Queue.Enqueue         O(1)                 O(n)             O(1)      
 List.Add              O(1)                 O(n)             O(log n)  
 HashSet.Add           O(1)                 O(n)             O(log n)  
 SortedSet.Add         O(log n)             O(n)             O(log n)  
 Dictionary.Add        O(1)                 O(n)             O(log n)  
 SortedDictionary.Add  O(log n)             O(n log n)       O(log n)  
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
于 2015-04-02T01:04:31.150 に答える
16

標準辞書はすでに十分に最適化されています。ルックアップを行うときに実際に行うことは、提供されたキーのハッシュを計算し (その速度はキーのタイプと実装方法によって異なりますGetHashCode)、ハッシュ値のモジュロ演算で正しいバケットを見つけてから、適切な値が見つかるまでバケットの内容を反復します (その速度はGetHashCode関数の品質に依存します。そのため、バケットがバランスが取れていて、アイテムが多すぎない場合、Equalsメソッドの速度はタイプ)。

全体として、ルックアップにはあまり効果がないため、大幅に高速な汎用データ構造を見つけることはできないと思います。ただし、辞書の使用方法によっては、より専門的なソリューションを構築できる可能性があります。たとえば、キーが型である非常に高速なルックアップが必要でした。辞書を使用して を実行する代わりに、dictionary[typeof(T)]次のようなジェネリック クラスを作成しました。

class ValueStore<T> 
{
  public static T Value;
}

したがってValueStore<T>.Value、ルックアップのオーバーヘッドがほとんどゼロで済みます。

同様のことができるかどうか (そして、それが価値があるかどうか) は、ユースケースによって異なります。構造体が保持するアイテムの数、読み書きの頻度、スレッドセーフである必要があるかどうか、書き込み速度の重要性など。たとえば、書き込み速度はまったく重要ではないが、スレッドセーフが必要な場合、書き込み速度を犠牲にして、データ構造が書き込まれることはなく、代わりにコピーされるコピーオンライトを実行する必要があります。特殊化すると、書き込み時に構造を並べ替えて最適化し、ハッシュ バケットに N 個を超えるアイテムが含まれないようにすることもできます。

追伸: 速度がどうしても必要であるが、より特化したデータ構造を構築できない場合はDictionary<TKey,TValue>、さまざまなサニティ チェック (null チェックなど) と仮想/インターフェイス メソッドの呼び出しをコピーして削除することで、わずかな利益が得られる可能性があります。ただし、これで 20% 以上の利益が得られるとは思えません。

于 2013-05-19T03:23:29.240 に答える