【问题标题】:C# dictionary - how to solve limit on number of items?C# 字典 - 如何解决项目数量限制?
【发布时间】:2011-10-24 14:03:26
【问题描述】:

我正在使用字典,我需要在其中存储近 13 000 000 个键。不幸的是,在添加第 11 950 000 个密钥后,我得到了一个异常“系统内存不足”。这个问题有什么解决办法吗?将来我需要我的程序在比我的实际功能更弱的计算机上运行..

我需要这么多键,因为我需要存储对 - 序列名称和序列长度,用于解决生物信息学相关问题。

我们将不胜感激。

【问题讨论】:

  • 内存中的 1300 万个键对于普通系统来说实在是太多了!
  • 所有这些都必须驻留在内存中吗?
  • 使用任何一个nosql-database.org
  • 您需要一本词典中的所有键吗?你不能把它分成多个块吗?
  • 等一下,1300 万个键也不算多 - 这里的值是多少?数组?

标签: c# dictionary limit


【解决方案1】:

购买更多内存,安装 64 位版本的操作系统并重新编译为 64 位。不,我不是在开玩笑。如果你想要这么多对象......在ram中......然后称之为“功能”。如果新的Android可以需要16gb的内存来编译...

我忘记了...您可以先阅读C# array of objects, very large, looking for a better way

你知道一千三百万个物体有多少吗?

为了进行比较,32 位 Windows 应用程序可以访问不到 2 GB 的地址空间。所以它是 20 亿字节(给予或接受)...... 20 亿 / 1300 万 = 大约 150 字节/对象。现在,如果我们考虑一个引用类型占用了多少……吃掉 150 个字节是很容易的。

我要补充一点:我查看了我的Magic 8-Ball,它告诉我:向我们展示您的代码。如果您不告诉我们您使用什么作为键和值,我们应该如何帮助您?你在使用什么,classstruct 或“原始”类型?告诉我们您的TKeyTValue 的“大小”。可悲的是我们的水晶球昨天坏了:-)

【讨论】:

  • Dictionary 非常节省内存。因此,将字典中的 13M 项放入 2 GB 的内存中并非不现实。但你是对的,它很容易超过它不再适合的尺寸。
  • @svick 从我看到的 Dictionary 实现来看,对于大数字,它会搜索 > 2 * Count 的素数,因此 > 2600 万。假设它是 2600 万,然后它创建一个 26M 的 int[] 数组和一个包含 2x int、1x TKey、1x TValue 的结构数组。所以纯粹的开销是 3x int * 26M = 12 * 26M = 300mb(我在看 Resize)。如果他很幸运并且能够在加倍之前填充它,那么它仍然是 150mb 的开销。
  • 是的,差不多。除了仅在调整大小时才搜索素数,因此它实际上是素数> Count。对于 13M 个对象,它在我的测试中使用的实际素数是 23,997,907。
  • @svick .NET 4.0 中的最后一个“In table”中的素数似乎是 7199369,所以在那之后我会认为他会找到大约 1400 万的东西
  • 实际上是使用表中倒数第二个数字(~6M),然后是手动计算(即大约翻倍)。所以 24M 适合。当然,这一切都假设没有设置默认容量。如果元素的数量已知,那么在这里设置容量可能会有很大帮助。
【解决方案2】:

C# 不是一种旨在解决繁重的科学计算问题的语言。绝对可能使用 C# 来构建工具来做你想做的事,但是像 Dictionary 这样的现成部件旨在解决更常见的业务问题,比如将邮政编码映射到城市等等之类的。

您将不得不使用某种外部存储。我的建议是购买一个数据库并用它来存储您的数据。然后使用DataSet或一些类似的技术将数据的部分加载到内存中,对其进行操作,然后将更多数据从数据库中倒入DataSet中,等等。

【讨论】:

  • 您可以简单地创建一个数据集(甚至是简单的文本文件),然后将信息保存到磁盘。然后,您可以根据需要仅将文件的一部分加载到内存中。我怀疑是否需要将 13m 键和值加载到内存中。
  • 只使用 SQLite,更简单的恕我直言
【解决方案3】:

嗯,我遇到了几乎完全相同的问题。

我想从数据库中将大约 1250 万个 [string, int] 加载到字典中(对于上面所有不明白为什么的编程“神”,答案是当你工作时它会快得多如果您可以在内存中缓存一部分关键表,则使用 150 GB 的数据库)。

它令人讨厌地在几乎同一个地方抛出了一个内存不足的异常——尽管该进程只消耗了大约 1.3 GB 的内存(在 db 中进行了明智的更改后减少到大约 800 MB 的内存,但它却在 1200 万大关之下)读取方法不要尝试一次全部完成) - 尽管在具有 8 GB 内存的 I7 上运行。

解决方案实际上非常简单 - 在解决方案资源管理器中的 Visual Studio (2010) 中,右键单击项目并选择属性。 在 Build 选项卡中将 Platform Target 设置为 x64 并重新构建。

它可以在几秒钟内将加载到字典中的负载嘎嘎作响,并且字典性能非常好。

【讨论】:

    【解决方案4】:

    简单的解决方案就是使用简单的数据库。在这种情况下,最明显的解决方案是恕我直言,使用 SQLite .NET ,快速、简单且内存占用少。

    【讨论】:

    • 正如其他 cmets/answers 中所讨论的,使用数据库并不总是一种选择。如果您出于性能原因想将所有内容都保存在内存中,那么使用数据库可能会影响您的性能。
    【解决方案5】:

    我认为您需要一种新的处理方法。

    我必须假设您从文件或数据库中获取数据,无论哪种方式都应该保留。

    除了增加系统内存之外,您实际上无法增加对字典中存储值数量的限制,但无论如何,处理如此大量的数据都是一种效率极低的方法。

    您应该重新考虑您的算法,以便以更易于管理的部分处理数据。这将意味着分阶段处理它,直到你得到你的结果。这可能意味着要经过数百次数据传递,但这是唯一的方法。

    我还建议您考虑使用泛型来帮助加快这种重复处理并减少内存使用。

    请记住,系统性能和对外部存储数据(无论是外部磁盘存储还是数据库)的访问之间仍然存在平衡。

    【讨论】:

      【解决方案6】:

      这不是 Dictionary 对象的问题,而是服务器中的可用内存问题。我已经进行了一些调查以了解字典对象的失败,但它从未失败过。以下是供您参考的代码

          private static void TestDictionaryLimit()
          {
              int intCnt = 0;
              Dictionary<long, string> dItems = new Dictionary<long, string>();
              Console.WriteLine("Total number of iterations = {0}", long.MaxValue);
              Console.WriteLine("....");
              for (long lngCnt = 0; lngCnt < long.MaxValue; lngCnt++)
              {
                  if (lngCnt < 11950020)
                      dItems.Add(lngCnt, lngCnt.ToString());
                  else
                      break;
                  if ((lngCnt % 100000).Equals(0))
                      Console.Write(intCnt++);
              }
              Console.WriteLine("Completed..");
              Console.WriteLine("{0} number of items in dictionary", dItems.Count);
          }
      

      上面的代码执行正常,存储的数量比你提到的要多。

      【讨论】:

      • 它与可用的物理内存无关。这完全是关于可用的地址空间。您是在具有 2GB 地址空间的 32 位进程上运行它,还是在具有 10 亿 GB 地址空间的 64 位进程上运行它?
      • @EricLippert 它只有 8TB 的地址空间,即使在更高端的 Windows Server 上也是如此 :-D 不要给新用户错误的希望 :-D
      【解决方案7】:

      真的 13000000 个项目是相当多的。 如果分配了 13000000 个类,那就是对垃圾收集器胃的一个非常深的打击!

      另外,如果你找到一种使用默认 .NET 字典的方法,性能会非常糟糕,键太多,键的数量接近 31 位散列可以使用的值的数量,无论如何性能都会很糟糕你使用的系统,当然,内存会太多!

      如果您需要一个可以使用比哈希表更多内存的数据结构,您可能需要一个自定义哈希表与自定义二叉树数据结构混合。 是的,可以编写自己的两个组合。

      对于这个如此奇怪和具体的问题,你不能肯定地依赖 .net 哈希表。

      考虑一棵树的查找复杂度为 O(log n),而构建复杂度为 O(n * log n),当然,构建它会太长。 然后,您应该构建一个二叉树哈希表(反之亦然),这样您就可以同时使用这两种数据结构,从而减少内存消耗。

      然后,考虑在 32 位模式下编译它,而不是在 64 位模式下:64 位模式使用更多内存来存储指针。 同时,可能相反,32 位地址空间可能不足以解决您的问题。 我从来没有遇到过可以用完 32 位地址空间的问题!

      如果键和值都是简单的值类型,我建议你在 C dll 中编写数据结构并通过 C# 使用它。

      你可以试着写一本字典。 比方说,您可以将数据分成 26 个字典之间的 500000 个项目块,但是占用的内存会非常大,不要认为您的系统会处理它。

      public class MySuperDictionary
      {
          private readonly Dictionary<KEY, VALUE>[] dictionaries;
      
          public MySuperDictionary()
          {
              this.dictionaries = new Dictionary<KEY, VALUE>[373]; // must be a prime number.
              for (int i = 0; i < dictionaries.Length; ++i)
                  dictionaries[i] = new Dicionary<KEY, VALUE>(13000000 / dictionaries.Length);
          }
      
          public void Add(KEY key, VALUE value)
          {
              int bucket = (GetSecondaryHashCode(key) & 0x7FFFFFFF) % dictionaries.Length;
              dictionaries[bucket].Add(key, value);
          }
      
          public bool Remove(KEY key)
          {
              int bucket = (GetSecondaryHashCode(key) & 0x7FFFFFFF) % dictionaries.Length;
              return dictionaries[bucket].Remove(key);
          }
      
          public bool TryGetValue(KEY key, out VALUE result)
          {
              int bucket = (GetSecondaryHashCode(key) & 0x7FFFFFFF) % dictionaries.Length;
              return dictionaries[bucket].TryGetValue(key, out result);
          }
      
          public static int GetSecondaryHashCode(KEY key)
          {
              here you should return an hash code for key possibly using a different hashing algorithm than the algorithm you use in inner dictionaries
          }
      }
      

      【讨论】:

      • 字典中的 13M 项并不一定意味着必须进行 GC 处理的 13M 对象。也就是说,如果键和值都是值类型(并且没有装箱)。而且您通常不受可用 RAM 的限制,而是受可用虚拟内存的限制。如果你的物理 RAM 用完了,应用程序可能会运行得非常慢,但它仍然会运行,不会抛出 OOM 异常。
      • @svick 如果它们在字典中,那么在内部它们在堆上的结构中。此外,如果它们是堆栈上的值类型,那么您会更快耗尽堆栈空间。
      • 不是真的 jon,.NET 字典,与 java 字典相反,如果我没记错的话,只使用恒定数量的 3 引用对象。如果键和值是值类型,则它们在内部存储在值结构数组(即值类型)和存储桶数组(即整数数组)中。所以.NET 字典写得很好而且速度很快。 .NET 垃圾收集器足够聪明,不会将纯值类型用于垃圾收集。
      • @JonHanna,是的,它们在堆上。但这并不意味着 GC 必须跟踪它们中的每一个。 GC 会跟踪整个字典(以及它在内部使用的恒定数量的数组),但不会跟踪其中的每个值(当然,除非它们是引用类型)。
      【解决方案8】:

      有了这么多键,您应该使用数据库或 memcache 之类的东西,同时换出存储中的缓存块。我怀疑你一次需要所有的东西,如果你这样做了,它就不可能在内存很少的低功率机器上工作。

      【讨论】:

      • 根据处理要求,也可以选择MapReduce
      猜你喜欢
      • 2016-03-28
      • 1970-01-01
      • 1970-01-01
      • 2016-10-08
      • 1970-01-01
      • 2012-03-23
      • 1970-01-01
      • 2019-02-06
      • 1970-01-01
      相关资源
      最近更新 更多