【问题标题】:OutOfMemoryException with big collection and OrderBy?OutOfMemoryException 与大集合和 OrderBy?
【发布时间】:2012-06-23 19:11:05
【问题描述】:

我有一个集合trends,其中包含大约 30kk 个元素。当我尝试在 linqpad 中执行以下代码时

trends.Take(count).Dump();

一切正常。

但是如果我添加排序:

 trends.OrderByDescending(x => x.Item2).Take(count).Dump();

我得到 System.OutOfMemoryException

我做错了什么?

【问题讨论】:

  • 您是否在内存中保存了该集合?
  • @phg 是的,它是 List> 我将这个集合保存在内存中
  • Take 将仅迭代第一个 count 项目。 OrderByDescending 将遍历整个集合,在内存中存储第二个排序集。
  • “kk”是“百万”的另一种说法吗? OrderByDescending 需要 O(n) 存储空间。
  • stackoverflow.com/questions/6076316/…(提供解决方案)

标签: c# .net out-of-memory


【解决方案1】:

OrderByDescending(或OrderBy)在您尝试获取第一个元素时实现整个序列 - 它必须这样做,否则您不可能知道第一个元素。它必须制作序列的副本(当然,通常只是一堆引用)才能排序,所以如果原始序列是内存中的集合,你最终会得到两个副本其中。大概你没有足够的内存。

【讨论】:

    【解决方案2】:

    您不必对整个集合进行排序,只需从中取出顶部的 count 元素即可。这是https://codereview.stackexchange.com/a/9777/11651 的解决方案。

    这个答案的重点是It doesn't require all items to be kept in memory(for sorting)

    再次来自链接中答案的cmets:

    这个想法是:您可以在 O(n) 时间内找到 List 的 Max(或 Min)项。如果您将此想法扩展到 m 项(问题中的 5 个),您可以更快地获得顶部(或底部)m 个项,然后对列表进行排序(只需一次通过列表 + 保留 5 个已排序项的成本)

    【讨论】:

      【解决方案3】:

      这是另一种可能比原始 LINQ 工作得更好的扩展方法(例如,它不应该因为少数选定的项目而崩溃)。像 L.B. 的解决方案一样,它应该是 O(n) 并且不会将所有项目都保留在内存中:

      public static class Enumerables
      {
          public static IEnumerable<T> TopN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count, IComparer<TV> comparer)
          {
              var qCount = 0;
              var queue = new SortedList<TV, List<T>>(count, comparer);
              foreach (var val in value)
              {
                  var currTv = selector(val);
                  if (qCount >= count && comparer.Compare(currTv, queue.Keys[0]) <= 0) continue;
                  if (qCount == count)
                  {
                      var list = queue.Values[0];
                      if (list.Count == 1)
                          queue.RemoveAt(0);
                      else
                          list.RemoveAt(0);
                      qCount--;
                  }
                  if (queue.ContainsKey(currTv))
                      queue[currTv].Add(val);
                  else
                      queue.Add(currTv, new List<T> {val});
                  qCount++;
              }
              return queue.SelectMany(kvp => kvp.Value);
          }
      
          public static IEnumerable<T> TopN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count)
          {
              return value.TopN(selector, count, Comparer<TV>.Default);
          }
      
          public static IEnumerable<T> BottomN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count, IComparer<TV> comparer)
          {
              return value.TopN(selector, count, new ReverseComparer<TV>(comparer));
          }
      
          public static IEnumerable<T> BottomN<T, TV>(this IEnumerable<T> value, Func<T, TV> selector, Int32 count)
          {
              return value.BottomN(selector, count, Comparer<TV>.Default);
          }
      }
      
      // Helper class
      public class ReverseComparer<T> : IComparer<T>
      {
          private readonly IComparer<T> _comparer;
      
          public int Compare(T x, T y)
          {
              return -1*_comparer.Compare(x, y);
          }
      
          public ReverseComparer()
              : this(Comparer<T>.Default)
          { }
      
          public ReverseComparer(IComparer<T> comparer)
          {
              if (comparer == null) throw new ArgumentNullException("comparer");
              _comparer = comparer;
          }
      }
      

      还有一些测试:

      [TestFixture]
      public class EnumerablesTests
      {
          [Test]
          public void TestTopN()
          {
              var input = new[] { 1, 2, 8, 3, 6 };
              var output = input.TopN(n => n, 3).ToList();
              Assert.AreEqual(3, output.Count);
              Assert.IsTrue(output.Contains(8));
              Assert.IsTrue(output.Contains(6));
              Assert.IsTrue(output.Contains(3));
          }
      
          [Test]
          public void TestBottomN()
          {
              var input = new[] { 1, 2, 8, 3, 6 };
              var output = input.BottomN(n => n, 3).ToList();
              Assert.AreEqual(3, output.Count);
              Assert.IsTrue(output.Contains(1));
              Assert.IsTrue(output.Contains(2));
              Assert.IsTrue(output.Contains(3));
          }
      
          [Test]
          public void TestTopNDupes()
          {
              var input = new[] { 1, 2, 8, 8, 3, 6 };
              var output = input.TopN(n => n, 3).ToList();
              Assert.AreEqual(3, output.Count);
              Assert.IsTrue(output.Contains(8));
              Assert.IsTrue(output.Contains(6));
              Assert.IsFalse(output.Contains(3));
          }
      
          [Test]
          public void TestBottomNDupes()
          {
              var input = new[] { 1, 1, 2, 8, 3, 6 };
              var output = input.BottomN(n => n, 3).ToList();
              Assert.AreEqual(3, output.Count);
              Assert.IsTrue(output.Contains(1));
              Assert.IsTrue(output.Contains(2));
              Assert.IsFalse(output.Contains(3));
          }
      }
      

      【讨论】:

      • 效果很好,除非集合有几个相同的元素(queue.Add(currTv, val) 抛出异常)
      • @Neir0 我刚刚修复了它以处理重复。
      猜你喜欢
      • 2013-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-22
      • 2020-06-02
      • 1970-01-01
      • 2012-03-06
      • 2017-02-18
      相关资源
      最近更新 更多