问题描述
本来我想知道是否了ToList
分配更多的内存比使用名单,其中的构造; T>
这需要一个的IEnumerable< T>
(无差异)。
有关测试的目的,我用 Enumerable.Range
来创建一个源阵列,我可以用它来创建名单,其中的一个实例; INT>
通过1。了ToList
和2的。两者都创建副本。
这是我如何来发现的内存消耗有很大的区别:
-
Enumerable.Range(1,10000000)
或 -
Enumerable.Range(1,10000000).ToArray()
在我使用的第一和调用了ToList
生成的对象ineeds〜比阵列(38,26MB / 64MB),60%以上的内存。
问:,这是什么原因或者是我的推理错误
VAR memoryBefore = GC.GetTotalMemory(真正的);
VAR范围= Enumerable.Range(1,10000000);
VAR rangeMem = GC.GetTotalMemory(真) - memoryBefore; // 微不足道
VAR列表= range.ToList();
VAR memoryList = GC.GetTotalMemory(真) - memoryBefore - rangeMem;
字符串memInfoEnumerable =的String.Format(记忆之前:{0:N2} MB列表:{1:N2} MB
(memoryBefore / 1024f)/ 1024f
(memoryList / 1024f)/ 1024f);
//记忆前:0,11 MB列表:64,00 MB
memoryBefore = GC.GetTotalMemory(真正的);
变种阵列= Enumerable.Range(1,10000000).ToArray();
VAR memoryArray = GC.GetTotalMemory(真) - memoryBefore;
表= array.ToList();
memoryList = GC.GetTotalMemory(真) - memoryArray;
字符串memInfoArray =的String.Format(记忆之前:{0:N2} MB数组:{1:N2} MB列表:{2:N2} MB
(memoryBefore / 1024f)/ 1024f
(memoryArray / 1024f)/ 1024f
(memoryList / 1024f)/ 1024f);
//记忆之前:64,11 MB阵:38,15 MB列表:38,26 MB
这可能涉及到使用添加到列表中时,调整为后盾缓存加倍算法。当您分配一个数组,那长度被称为,然后可以通过检查 IList的查询[< T>]
和/或的ICollection [< T>]
;因此,它可以分配一个阵列,大小合适的第一次,然后就块复制的内容。
随着序列,这是不可能的(顺序不公开以任何方式访问的长度);因此,它必须改为退回到保持填补了缓冲;如果已满,则双击它,然后复制。
显然,这需要大约两倍的内存。
这是有趣的测试将是:
VAR列表=新的名单,其中,INT>(10000000);
list.AddRange(Enumerable.Range(1,10000000));
这将首先分配适当的大小,同时仍使用顺序。
TL;博士;构造,当传递的序列,首先检查,看它是否可以转换为一公知的界面获得的长度。
Originally i wanted to know whether ToList
allocates more memory than using the constructor of List<T>
which takes an IEnumerable<T>
(no difference).
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:
Enumerable.Range(1, 10000000)
orEnumerable.Range(1, 10000000).ToArray()
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"
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.
An interesting test would be:
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;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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!