本来我想知道是否ToList
分配比使用 List<T>
的构造函数更多的内存这需要 IEnumerable<T>
(没有不同)。
出于测试目的,我使用了 Enumerable.Range
创建一个源数组,我可以用它来创建 List<int>
的实例通过 1. ToList
和 2. constructor .两者都在创建副本。
这就是我如何注意到内存消耗的巨大差异:
-
Enumerable.Range(1, 10000000)
或 -
Enumerable.Range(1, 10000000).ToArray()
当我使用第一个并调用 ToList
时生成的对象需要比数组多 60% 的内存 (38,26MB/64MB)。
问:这是什么原因,或者我的推理错误在哪里?
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[<T>]
来查询和/或 ICollection[<T>]
;因此它可以分配一个单一的数组,第一次调整大小,然后只是 block 复制内容。
对于序列这是不可能的(序列不会以任何可访问的方式公开长度);因此它必须退回到“继续填充缓冲区;如果已满,将其加倍并复制”。
显然这需要大约两倍的内存。
一个有趣的测试是:
var list = new List<int>(10000000);
list.AddRange(Enumerable.Range(1, 10000000));
这将在最初分配正确的大小,同时仍然使用序列。
tl;博士;构造函数在传递一个序列时,首先检查它是否可以通过转换为众所周知的接口(interface)来获取长度。
关于c# - Enumerable.Range 的高内存消耗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10519275/