繁体   English   中英

LINQ orderby为什么要消耗更多的内存?

[英]Why does LINQ orderby consume more memory?

我想知道为什么orderBy会消耗更多的内存,然后只复制列表并进行排序。

void printMemoryUsage()
{
    long memory = GC.GetTotalMemory(true);
    long mb = 1024 * 1024;
    Console.WriteLine("memory: " + memory/mb + " MB" );
}

var r = new Random();
var list = Enumerable.Range(0, 20*1024*1024).OrderBy(x => r.Next()).ToList();

printMemoryUsage();
var lsitCopy = list.OrderBy(x => x);
foreach(var v in lsitCopy)
{
    printMemoryUsage();
    break;
}

Console.ReadKey();

我得到的结果是:

内存:128 MB

内存:288 MB

但是复制列表和排序消耗的内存更少。

void printMemoryUsage()
{
    long memory = GC.GetTotalMemory(true);
    long mb = 1024 * 1024;
    Console.WriteLine("memory: " + memory/mb + " MB" );
}

var r = new Random();
var list = Enumerable.Range(0, 20*1024*1024).OrderBy(x => r.Next()).ToList();

printMemoryUsage();


var lsitCopy = list.ToList();
printMemoryUsage();
lsitCopy.Sort();
printMemoryUsage();

Console.ReadKey();

结果是:

内存:128 MB

内存:208 MB

内存:208 MB

更多测试表明orderBy消耗的内存是列表大小的两倍。

当您深入研究内部如何实现这两种方法时,这并不奇怪。 看看.NET的参考源

在第二种方法中,您在列表上调用Sort()方法,将List对象中的内部数组传递到用C#以外的本机代码编写的TrySZSort方法,这意味着垃圾回收器无法工作。

private static extern bool TrySZSort(Array keys, Array items, int left, int right);

现在,在第一种方法中,您正在使用LINQ对可枚举进行排序。 当您调用.OrderBy()OrderedEnumerable<T>是在构造一个OrderedEnumerable<T>对象。 仅仅调用OrderBy并不能对列表进行排序。 它仅在被调用的GetEnumerator方法枚举时才进行排序。 当您调用ToList或使用foreach类的枚举枚举时,将在后台隐式调用GetEnumerator

实际上,您对列表进行了两次排序,因为在此行上一次对列表进行了枚举:

var list = Enumerable.Range(0, 20*1024*1024).OrderBy(x => r.Next()).ToList();

当您通过此行上的foreach枚举时,又一次:

var lsitCopy = list.OrderBy(x => x);
foreach(var v in lsitCopy)

由于这些LINQ方法未使用本机代码,因此它们依赖垃圾回收器来处理。 每个类还创建了一堆对象(例如, OrderedEnumerable使用数组的另一个副本创建Buffer<TElement> )。 所有这些对象都消耗RAM。

我必须对此进行一些研究,并发现了一些有趣的信息。 默认的List.Sort函数执行就地排序(不是第二个副本),但是通过调用Array.Sort来执行某些操作,而Array.Sort最终调用TrySZSort,TrySZSort是经过高度优化的本机非托管CLR函数,用于选择特定的排序该算法基于输入类型,但在大多数情况下执行所谓的自省排序,该合并结合了QuickSort,HeapSort和InsertSort的最佳用例,以实现最大效率。 这是在非托管代码中完成的,这意味着它通常更快,更高效。

如果你有兴趣在下降的兔子洞,数组排序源是在这里和TrySZSort实现是在这里 但是最终,使用非托管代码意味着不涉及垃圾收集器,因此使用的内存更少。

OrderBy使用的实现是标准的Quicksort,而OrderedEnumerable实际上创建了排序中使用的键的第二个副本(在您的情况下为唯一字段,但是如果您考虑使用更大的类对象,则不必如此)并使用一个或两个单个属性作为排序器),从而得到与您所观察到的完全相同的结果,即额外使用量等于第二个副本的集合大小。 假设您随后将其输入到列表或数组(而不是OrderedEnumerable)中,然后等待或强制进行垃圾回收,则应恢复该内存的大部分。 该Enumerable.OrderBy法源是在这里 ,如果你想在挖掘它。

可以在在线创建的OrderedEnumerable实现中找到使用的额外内存的来源

IOrderedEnumerable<int> lsitCopy = list.OrderBy(x => x);

OrderedEnumerable是一个通用实现,可以根据您提供的任何条件对其进行排序,这与List.Sort的实现(仅按值对元素进行排序)明显不同。 如果遵循OrderedEnumerable的编码,则会发现它会创建一个缓冲区 ,将您的值复制到该缓冲区中 ,从而增加了80MB(4 * 20 * 1024 * 1024)的内存。 额外的40MB(2 * 20 * 1024 * 1024)与为通过键对列表进行排序而创建的结构相关联。

要注意的另一件事是,不仅OrderBy(x => x)会导致更多的内存使用,而且还使用了更多的处理能力,根据我的测试调用Sort比使用OrderBy(x => x)快约6倍。

List.Sort()方法由本机实现的高度优化方法支持,该方法用于按元素的值对元素进行排序,而Linq OrderBy方法则用途更广,因此对于按值对列表进行简单排序的优化程度也较低。

IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)

PS:我建议您停止使用var而不是实际的变量类型,因为它向代码读者隐藏了有关代码实际功能的宝贵信息。 我建议开发人员仅将var关键字用于匿名类型

康纳的回答给出了一个线索,这里发生了什么。 OrderedEnumerable的实现使其更加清晰。 OrderedEnumerable的GetEnumerator是

    public IEnumerator<TElement> GetEnumerator() {
        Buffer<TElement> buffer = new Buffer<TElement>(source);
        if (buffer.count > 0) {
            EnumerableSorter<TElement> sorter = GetEnumerableSorter(null);
            int[] map = sorter.Sort(buffer.items, buffer.count);
            sorter = null;
            for (int i = 0; i < buffer.count; i++) yield return buffer.items[map[i]];
        }
    }

缓冲区是原始数据的另一个副本。 Map保留订单的映射。 所以,如果代码是

// memory_foot_print_1
var sortedList = originalList.OrderBy(v=>v)
foreach(var v in sortedList)
{
// memory_foot_print_2
...
}

在这里,memory_foot_print_2将等于memory_foot_print_1 + size_of(originalList)+ size_of(new int [count_of(originalList)]))(假设没有GC)

因此,如果originalList是大小为80Mb的整数的列表,则memory_foot_print_2-memory_foot_print_1 = 80 + 80 = 160Mb。 并且如果originalList是大小为80Mb的日志列表,则我正在观察的是memory_foot_print_2-memory_foot_print_1 = 80+ 40(地图大小)= 120Mb(假设int-4个字节,longs- 8个字节)。

这就引出了另一个问题,对大型对象使用OrderBy是否有意义。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM