Enumerable.Range 的高内存消耗? [英] High memory consumption with Enumerable.Range?

查看:34
本文介绍了Enumerable.Range 的高内存消耗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

本来我想知道 ToList 是否比使用 List 的构造函数分配更多的内存,它采用 IEnumerable(没有不同).

Originally i wanted to know whether ToList allocates more memory than using the constructor of List<T> which takes an IEnumerable<T> (no difference).

出于测试目的,我使用 Enumerable.Range 创建了一个源数组,我可以使用该数组通过 1.ToList 创建 List 的实例 和 2.构造函数.两者都在创建副本.

For test purposes i used Enumerable.Range to create a source array that i could use to create an instance of List<int> via 1.ToList and 2.constructor. Both are creating copies.

这就是我注意到内存消耗的巨大差异的原因:

This is how I came to notice a great difference in memory consumption between:

  1. Enumerable.Range(1, 10000000)
  2. Enumerable.Range(1, 10000000).ToArray()

当我使用第一个并调用 ToList 时,结果对象需要比数组多 60% 的内存(38,26MB/64MB).

When i use the first and call ToList the resulting object ineeds ~60% more memory than the Array(38,26MB/64MB).

问:这是什么原因或者我的推理错误在哪里?

Q: What is the reason for this or where is my error in reasoning?

var memoryBefore = GC.GetTotalMemory(true);
var range = Enumerable.Range(1, 10000000);
var rangeMem = GC.GetTotalMemory(true) - memoryBefore; // negligible
var list = range.ToList();
var memoryList = GC.GetTotalMemory(true) - memoryBefore - rangeMem;

String memInfoEnumerable = String.Format("Memory before: {0:N2} MB List: {1:N2} MB"
    , (memoryBefore / 1024f) / 1024f
    , (memoryList   / 1024f) / 1024f);
// "Memory before: 0,11 MB List: 64,00 MB"

memoryBefore = GC.GetTotalMemory(true);
var array = Enumerable.Range(1, 10000000).ToArray();
var memoryArray = GC.GetTotalMemory(true) - memoryBefore;
list = array.ToList();
memoryList = GC.GetTotalMemory(true) - memoryArray;

String memInfoArray = String.Format("Memory before: {0:N2} MB Array: {1:N2} MB List: {2:N2} MB"
   , (memoryBefore / 1024f) / 1024f
   , (memoryArray  / 1024f) / 1024f
   , (memoryList   / 1024f) / 1024f);
// "Memory before: 64,11 MB Array: 38,15 MB List: 38,26 MB"

推荐答案

这可能与添加到列表时用于调整后备缓冲区大小的加倍算法有关.当您作为数组分配时,的长度是已知的,并且可以通过检查 IList[] 和/或 ICollection[];因此它可以分配单个数组,第一次正确大小,然后只是块复制内容.

This probably relates to the doubling algorithm used to resize the backing buffer when adding to a list. When you allocate as an array, the length of that is known, and can be queried by checking for IList[<T>] and/or ICollection[<T>]; thus it can allocate a single array, right-sized the first time, and then just block-copy the contents.

使用序列这是不可能的(序列不会以任何可访问的方式暴露长度);因此它必须回退到继续填充缓冲区;如果已满,则将其加倍并复制".

With the sequence this is not possible (the sequence does not expose the length in any accessible way); thus it must instead fall back to "keep filling up the buffer; if full, double it and copy".

显然这需要大约两倍的内存.

Obviously this needs approx double the memory.

一个有趣的测试是:

var list = new List<int>(10000000);
list.AddRange(Enumerable.Range(1, 10000000));

这将最初分配正确的大小,同时仍然使用序列.

This will allocate the right size initially, while still using the sequence.

tl;博士;构造函数在传递序列时首先检查它是否可以通过转换为众所周知的接口来获取长度.

tl;dr; the constructor, when passed a sequence, first checks to see if it can obtain the length by casting to a well-known interface.

这篇关于Enumerable.Range 的高内存消耗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆