【问题标题】:Immutable Dictionary Vs Dictionary Vs C5不可变字典与字典与 C5
【发布时间】:2013-05-12 20:26:21
【问题描述】:

我们的应用程序使用了大量的字典,这些字典具有不经常更改的多级查找。我们正在研究将一些使用字典进行大量查找的关键代码转换为使用替代结构来实现 - 更快的查找,内存/gc 的轻量级。这使我们可以比较各种可用的字典/库 -

Dictionary(System.Collections.Generics.Dictionary-SCGD)、ImmutableDictionaryC5.HashDictionaryFSharpMap

运行以下程序并使用各种项目计数 - 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);
            }
        }
    }
}

【问题讨论】:

  • 似乎要编写一个公平的测试,您需要使其成为多线程,这是不可变集合的预期用途。使用 lock() 或 RWLockSlim 比较不可变与并发以及与您自己的锁定方案。

标签: c# performance dictionary f# immutability


【解决方案1】:

在 .NET 3.1 中,它们看起来非常相似。

|    Method | MAXITEMS |      Mean |     Error |    StdDev |
|---------- |--------- |----------:|----------:|----------:|
|       imm |       10 | 0.9921 ns | 0.0630 ns | 0.1837 ns |
|       scg |       10 | 0.9699 ns | 0.0571 ns | 0.1683 ns |
| scgsorted |       10 | 1.0136 ns | 0.0577 ns | 0.1684 ns |
|       imm |      100 | 1.5296 ns | 0.1153 ns | 0.3327 ns |
|       scg |      100 | 1.3151 ns | 0.0694 ns | 0.1990 ns |
| scgsorted |      100 | 1.4516 ns | 0.0855 ns | 0.2426 ns |
|       imm |     1000 | 0.8514 ns | 0.0905 ns | 0.2582 ns |
|       scg |     1000 | 1.0898 ns | 0.0552 ns | 0.1416 ns |
| scgsorted |     1000 | 1.0302 ns | 0.0533 ns | 0.1001 ns |
|       imm |    10000 | 1.0280 ns | 0.0530 ns | 0.1175 ns |
|       scg |    10000 | 0.9929 ns | 0.0523 ns | 0.1253 ns |
| scgsorted |    10000 | 1.0531 ns | 0.0534 ns | 0.1248 ns |

测试源码:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace CollectionsTest
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Immutable;
    using System.Runtime;

    /// <summary>
    ///
    /// </summary>
    public class Program
    {
        private static ImmutableDictionary<string, object> idictionary;
        private static Dictionary<string, object> dictionary;
        private static SortedDictionary<string, object> sorteddictionary;
        private static string[] randomIndexes;

        [Params(10, 100, 1000, 10000)] public  int MAXITEMS { get; set; }

        public Program()
        {
            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
            randomIndexes = randomIndexesList.ToArray();

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

            // IMMU
            idictionary = listofkvps.ToImmutableDictionary();
            //Console.WriteLine(idictionary.Count);

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

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


            //scg(randomIndexes, dictionary);


            //imm(randomIndexes, idictionary);
        }

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



            Console.WriteLine(BenchmarkRunner.Run<Program>());
        }

        [Benchmark]
        public   void imm()
        {
            for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
            {
                string i = randomIndexes[index];
                object value;
                idictionary.TryGetValue(i, out value);
            }
        }

        [Benchmark]
        public   void scg()
        {
            // TEST ------------------------------------------------------------------------------------------------
            for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
            {
                string i = randomIndexes[index];
                object value;
                dictionary.TryGetValue(i, out value);
            }
        }

        [Benchmark]
        public   void scgsorted()
        {
            // TEST ------------------------------------------------------------------------------------------------
            for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
            {
                string i = randomIndexes[index];
                object value;
                sorteddictionary.TryGetValue(i, out value);
            }
        }
    }
}

【讨论】:

    【解决方案2】:

    您认为不可变字典允许更快查找的假设是错误的,因为几乎所有不可变集合设法避免在“修改”时复制整个结构的方法是将数据存储在树中并仅在“修改”时复制部分节点,共享所有其他节点。而且树访问通常比通过索引访问平面数组要慢,就像可变表亲所做的那样。

    我根据你的代码比较了Dictionary&lt;,&gt;ConcurrentDictionary&lt;,&gt;ImmutableDictionary&lt;,&gt;单线程读取性能

    热身后 30 次跑步的平均结果

    为了了解写入性能,我还进行了一项测试,向字典中添加了 50 个条目。同样,热身后 30 次运行的平均结果

    经过测试

    • .net 4.5.1
    • Microsoft Bcl 不可变 1.0.34.0

    注意应该注意的是,不可变字典的速度要快得多和/或允许在许多现实生活中的多线程应用程序中实现更高级别的并发性,否则这些应用程序将不得不求助于缓慢或容易出错的技术,例如防御性复制,锁定等,以应对线程面对的可变性。如果您需要快照能力,例如乐观并发、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
    

    您的问题是关于(冻结字典的)读取性能,但不可变集合的树特征在write performance 中显示类似:

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                           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)  
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    

    【讨论】:

    • 非常好的总结和比较。应该注意的是,您可以通过使用ImmutableDictionary.Builder 来提高ImmutableDictionary 的性能,这可以提高添加大量项目和/或创建的性能。否则,一次添加一个需要每次创建结构的(部分)副本。当然,这是否有益取决于您的情况:如果您初始化大量并且很少添加项目,则使用构建器是要走的路。
    【解决方案3】:

    这里还有一些基准测试。

    System.Collections.Immutable 包的 .NET 4.5 版本 1.3.1,64 位。

    DictionaryImmutableDictionary:

    BuilderBenchmark elapsed time
     Mutable   : Avg= 15.213, Stdev  4.591 [ms] (   10.2x)
     Immutable : Avg=155.883, Stdev 15.145 [ms]
    BuilderBenchmark per op
     Mutable   : Avg=  0.152, Stdev  0.046 [us] (   10.2x)
     Immutable : Avg=  1.559, Stdev  0.151 [us]
    SetItemBenchmark elapsed time
     Mutable   : Avg= 13.100, Stdev  2.975 [ms] (   30.4x)
     Immutable : Avg=397.932, Stdev 18.551 [ms]
    SetItemBenchmark per op
     Mutable   : Avg=  0.131, Stdev  0.030 [us] (   30.4x)
     Immutable : Avg=  3.979, Stdev  0.186 [us]
    LookupBenchmark elapsed time
     Mutable   : Avg=  9.439, Stdev  0.942 [ms] (    3.6x)
     Immutable : Avg= 34.250, Stdev  3.457 [ms]
    LookupBenchmark per op
     Mutable   : Avg=  0.094, Stdev  0.009 [us] (    3.6x)
     Immutable : Avg=  0.343, Stdev  0.035 [us]
    

    DictionaryImmutableSortedDictionary:

    BuilderBenchmark elapsed time
     Mutable   : Avg= 13.654, Stdev  5.124 [ms] (   34.5x)
     Immutable : Avg=471.574, Stdev 20.719 [ms]
    BuilderBenchmark per op
     Mutable   : Avg=  0.137, Stdev  0.051 [us] (   34.5x)
     Immutable : Avg=  4.716, Stdev  0.207 [us]
    SetItemBenchmark elapsed time
     Mutable   : Avg= 11.838, Stdev  0.530 [ms] (   37.6x)
     Immutable : Avg=444.964, Stdev 11.125 [ms]
    SetItemBenchmark per op
     Mutable   : Avg=  0.118, Stdev  0.005 [us] (   37.6x)
     Immutable : Avg=  4.450, Stdev  0.111 [us]
    LookupBenchmark elapsed time
     Mutable   : Avg=  9.354, Stdev  0.542 [ms] (    4.4x)
     Immutable : Avg= 40.988, Stdev  3.242 [ms]
    LookupBenchmark per op
     Mutable   : Avg=  0.094, Stdev  0.005 [us] (    4.4x)
     Immutable : Avg=  0.410, Stdev  0.032 [us]
    

    我想知道不可变集合会慢多少。请注意,整个运行是针对 100,000 次插入操作。我很高兴地报告查找性能下降仅为 4 倍,而插入性能下降为 10 倍,仍然相当不错。 ImmutableDictionary 明显优于 ImmutableSortedDictionary,除非你绝对需要排序的数据结构。

    关于写入时复制行为的旁注。这些持久性数据结构比任何简单的写入时复制都要快几个数量级。这是我正在使用的。使用比较和交换CAS 指令提交更改(通过数据竞争检测)也非常容易。

    附件

    程序.cs:

    using System;
    using System.Collections.Generic;
    using System.Collections.Immutable;
    using System.Diagnostics;
    using System.Linq;
    
    using ImmutableDictionary = System.Collections.Immutable.ImmutableDictionary; // select implementation to benchmark here
    
    namespace DictPerf
    {
        class Program
        {
            static string alphaNum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    
            static string NextString(Random r, char[] buf)
            {
                int i = 0, len = r.Next(buf.Length) + 1;
                for (; i < len; i++)
                {
                    buf[i] = alphaNum[r.Next(alphaNum.Length)];
                }
                return new string(buf, 0, len);
            }
    
            static HashSet<string> strings = new HashSet<string>();
    
            private static void Seed()
            {
                var r = new Random();
                var buf = new char[64];
                for (int i = 0; i < 100000; i++)
                {
                    strings.Add(NextString(r, buf));
                }
            }
    
            static void Main(string[] args)
            {
                Seed();
    
                Benchmark(RunDictionaryBuilderBenchmark, RunImmutableDictionaryBuilderBenchmark, "BuilderBenchmark");
                Benchmark(RunDictionarySetItemBenchmark, RunImmutableDictionarySetItemBenchmark, "SetItemBenchmark");
                Benchmark(RunDictionaryLookupBenchmark, RunImmutableDictionaryLookupBenchmark, "LookupBenchmark");
            }
    
            private static string Stats(IEnumerable<double> source)
            {
                var avg = source.Average();
                var variance = source.Select(val => (val - avg) * (val - avg)).Sum();
                var stdev = Math.Sqrt(variance / (source.Count() - 1));
                return $"Avg={avg,7:0.000}, Stdev{stdev,7:0.000}";
            }
    
            private static void Benchmark(Action<ICollection<string>, Stopwatch> benchmark1, Action<ICollection<string>, Stopwatch> benchmark2, string benchmarkName)
            {
                var xs = new List<double>();
                var ys = new List<double>();
    
                var sw = Stopwatch.StartNew();
                for (int i = 0; i < 10; i++)
                {
                    sw.Restart();
                    benchmark1(strings, sw);
                    xs.Add(sw.Elapsed.TotalMilliseconds);
                    sw.Restart();
                    benchmark2(strings, sw);
                    ys.Add(sw.Elapsed.TotalMilliseconds);
                }
    
                var x = xs.Average();
                var y = ys.Average();
                var a = xs.Select(v => v / 100).Average();
                var b = ys.Select(v => v / 100).Average();
    
                Console.WriteLine($"{benchmarkName} elapsed time");
                Console.WriteLine($" Mutable   : {Stats(xs)} [ms] ({y / x,7:0.0}x)");
                Console.WriteLine($" Immutable : {Stats(ys)} [ms]");
                Console.WriteLine($"{benchmarkName} per op");
                Console.WriteLine($" Mutable   : {Stats(xs.Select(v => v / 100))} [us] ({b / a,7:0.0}x)");
                Console.WriteLine($" Immutable : {Stats(ys.Select(v => v / 100))} [us]");
            }
    
            private static void RunDictionaryBuilderBenchmark(ICollection<string> strings, Stopwatch sw)
            {
                var d = new Dictionary<string, object>();
                foreach (var s in strings)
                {
                    d[s] = null;
                }
            }
    
            private static void RunImmutableDictionaryBuilderBenchmark(ICollection<string> strings, Stopwatch sw)
            {
                var d = ImmutableDictionary.CreateBuilder<string, object>();
                foreach (var s in strings)
                {
                    d[s] = null;
                }
                d.ToImmutableDictionary();
            }
    
            private static void RunDictionarySetItemBenchmark(ICollection<string> strings, Stopwatch sw)
            {
                var d = new Dictionary<string, object>();
                foreach (var s in strings)
                {
                    d[s] = null;
                }
            }
    
            private static void RunImmutableDictionarySetItemBenchmark(ICollection<string> strings, Stopwatch sw)
            {
                var d = ImmutableDictionary.Create<string, object>();
                foreach (var s in strings)
                {
                    d = d.SetItem(s, null);
                }
            }
    
            private static void RunDictionaryLookupBenchmark(ICollection<string> strings, Stopwatch timer)
            {
                timer.Stop();
    
                var d = new Dictionary<string, object>();
                foreach (var s in strings)
                {
                    d[s] = null;
                }
    
                timer.Start();
    
                foreach (var s in strings)
                {
                    object v;
                    d.TryGetValue(s, out v);
                }
            }
    
            private static void RunImmutableDictionaryLookupBenchmark(ICollection<string> strings, Stopwatch timer)
            {
                timer.Stop();
    
                var d = ImmutableDictionary.CreateBuilder<string, object>();
                foreach (var s in strings)
                {
                    d[s] = null;
                }
                var x = d.ToImmutableDictionary();
    
                timer.Start();
    
                foreach (var s in strings)
                {
                    object v;
                    x.TryGetValue(s, out v);
                }
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      标准字典已经很好地优化了。当您进行查找时,它真正做的就是计算所提供键的哈希(其速度取决于键的类型以及它如何实现GetHashCode),对哈希值进行模运算以找到正确的存储桶然后它遍历桶的内容,直到找到正确的值(其速度取决于GetHashCode 函数的质量,所以如果桶平衡良好并且不包含太多项目,并且类型的Equals 方法的速度)。

      总而言之,它对查找的作用不大,因此我认为您无法找到速度明显更快的通用数据结构。但是,根据您计划使用字典的方式,您可能能够构建一个更专业的解决方案。例如,我需要一个非常快速的查找,其中键是一个类型。我没有使用字典并执行dictionary[typeof(T)],而是创建了一个像这样的通用类:

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

      所以我可以在查找开销几乎为零的情况下执行ValueStore&lt;T&gt;.Value

      您是否可以做类似的事情(以及是否值得)真的取决于您的用例;结构将容纳多少项目,读取和写入的频率,是否需要线程安全,写入速度有多重要等。例如,如果写入速度根本不重要,但是否需要线程安全,您需要执行写时复制,其中数据结构永远不会被写入,而是被复制,以牺牲写入速度为代价确保线程安全和无锁(因此:快速)读取。专门化它可以在写入时重新排序结构以优化它,使哈希桶不包含超过 N 个项目。

      PS:如果您真的非常渴望速度但无法构建更专业的数据结构,那么您可能会从复制 Dictionary&lt;TKey,TValue&gt; 并删除各种健全性检查(空检查等)和虚拟/接口中获得少量收益方法调用。但是,如果那样的话,我怀疑这会给你带来超过 20% 的收益。

      【讨论】:

      • 这是很好的信息。也许你也可以回答他关于FSharpMapDictionary&lt;TKey, TValue&gt; 的问题?
      • @JulianR,很棒的见解。我们的字典平均包含 100-150 项,具有非常繁重的读取和不频繁的更新/写入,仅包含没有 GetHashCode( ) 覆盖的基本类型(这些默认值完全依赖于 .NET)。 Dictionary 是当时显而易见的选择。在最近的 async/tpl 支持下,我们不得不用锁包装读取以保证线程安全,因此研究了不可变结构是否有助于优化/避免锁。考虑到不可变库的查找成本,目前字典似乎仍然是赢家。
      • @sraj - 如果您需要线程安全并且只有很少的写入和很少的键,我肯定会考虑写入时复制而不是锁定。基本上,每当您修改字典时,不要将其添加到原始字典中,而是制作副本并将其添加到副本中,然后原子地交换副本和原始字典。示例:stackoverflow.com/questions/10675400/…
      • 我喜欢你的 ValueStore&lt;T&gt; 想法。我希望下次我有它的用例时我记得它:-)
      • ValueStore 显然只有在编译时知道 T 时才有用,其中存储在字典中,类型在编译时可能是未知的,因为代码可能只有一个指向接口的指针或一个基类。 ValueStore 需要注意的另一个问题是,如果实现依赖于具体类型,那么有人不能简单地传递派生版本,因为 T 可能是基类版本,如果不是确切的版本,则无法定位。基本上这打破了 Liskov,但嘿,我说这可能完全没问题,这取决于你在做什么。 #it​​syourfoot
      【解决方案5】:

      F# 映射结构被实现为二叉树,因此实际上不是字典。如 here 所述,标准 .net 字典是您将获得的最快速度。

      【讨论】:

      • 与 ImmutableDictionary 相同。它也是一棵二叉树,因此您可以在不复制整个字典的情况下有效地对其进行变异。如果您需要超快的查找时间,可变字典是最好的选择,您可以将其公开为 IReadOnlyDictionary 以赋予它听起来像您需要的只读特性。
      • @AndrewArnott,是的,但只读与不可变不同。第一种意味着 you 只能阅读,但其他消费者可以变异,因此在 MT 应用程序中很危险。第二种方式:保证内容不会被任何人修改,因此非常适合 MT 应用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多