我经常遇到这样的情况:我想在我声明查询的地方执行查询。这通常是因为我需要对它进行多次迭代,而且计算成本很高。例如:
string raw = "...";
var lines = (from l in raw.Split('\n')
let ll = l.Trim()
where !string.IsNullOrEmpty(ll)
select ll).ToList();这个很好用。但是,如果我不打算修改结果,那么我最好调用ToArray()而不是ToList()。
然而,我想知道ToArray()是否是通过第一次调用ToList()实现的,因此,与调用ToList()相比,内存效率更低。
我疯了吗?我是否应该在知道内存不会被分配两次的情况下调用ToArray() -安全和安全?
发布于 2013-05-01 17:42:23
除非您只需要一个数组来满足其他约束,否则您应该使用ToList。在大多数情况下,ToArray将分配比ToList更多的内存。
两者都使用数组作为存储,但是ToList有一个更灵活的约束。它需要数组至少与集合中的元素数一样大。如果数组更大,这不是问题。然而,ToArray需要数组的大小与元素的数量完全一致。
为了满足这一限制,ToArray通常比ToList多分配一次。一旦它有一个足够大的数组,它就会分配一个大小完全正确的数组,并将元素复制回该数组中。唯一可以避免这种情况的是,数组的grow算法恰好与需要存储的元素数(绝对是少数元素)相吻合。
编辑
有几个人问我,在List<T>值中拥有额外的未使用内存会带来什么后果。
这是一个值得关注的问题。如果创建的集合寿命很长,在创建后从未修改过,并且很有可能在Gen2堆中登陆,那么您最好先考虑ToArray的额外分配。
总的来说,我觉得这是比较少见的情况。更常见的是看到许多ToArray调用立即传递给其他短暂的内存使用,在这种情况下,ToList显然更好。
这里的关键是配置文件,配置文件,然后再配置更多。
发布于 2009-07-09 19:33:54
由于List<T>是作为一个动态大小的数组实现的,因此性能差异将是微不足道的。调用ToArray() (使用内部Buffer<T>类来增长数组)或调用ToList() (调用List<T>(IEnumerable<T>)构造函数)最终都需要将它们放入数组并扩展数组,直到它们全部满足。
如果您希望具体确认这一事实,请查看Reflector中所讨论的方法的实现--您将看到它们归结为几乎相同的代码。
发布于 2020-02-06 21:23:32
编辑2: (这是对原始答案的更正)
使用Benchmark.NET,我们可以通过性能度量来确认,接受的答案实际上是正确的:在一般情况下,ToList更快,因为它不需要从分配的缓冲区中修剪空空间。ToArray可能会执行额外的分配和复制操作,以使缓冲区的大小与元素的数量完全一致。
为了确认这一点,使用了以下基准测试。
[MemoryDiagnoser]
[ShortRunJob]
public class Benchmarks
{
[Params(0, 1, 6, 10, 42, 100, 1337, 10000)]
public int Count { get; set; }
public IEnumerable<int> Items => Enumerable.Range(0, Count).Where(i => i > 0);
[Benchmark(Baseline = true)]
public int[] ToArray() => Items.ToArray();
[Benchmark]
public List<int> ToList() => Items.ToList();
}结果证实,ToList在大多数情况下是10% - 15%的速度。
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000
Intel Core i9-10885H CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.302
[Host] : .NET 6.0.7 (6.0.722.32202), X64 RyuJIT
DefaultJob : .NET 6.0.7 (6.0.722.32202), X64 RyuJIT
| Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated |
|-------- |------ |-------------:|-----------:|-----------:|------:|--------:|--------:|-------:|----------:|
| ToArray | 0 | 29.73 ns | 0.546 ns | 0.536 ns | 1.00 | 0.00 | 0.0067 | - | 56 B |
| ToList | 0 | 31.51 ns | 0.485 ns | 0.405 ns | 1.06 | 0.02 | 0.0105 | - | 88 B |
| | | | | | | | | | |
| ToArray | 1 | 37.36 ns | 0.314 ns | 0.294 ns | 1.00 | 0.00 | 0.0114 | - | 96 B |
| ToList | 1 | 36.75 ns | 0.605 ns | 0.537 ns | 0.98 | 0.01 | 0.0153 | - | 128 B |
| | | | | | | | | | |
| ToArray | 6 | 100.05 ns | 1.522 ns | 1.349 ns | 1.00 | 0.00 | 0.0286 | - | 240 B |
| ToList | 6 | 85.16 ns | 0.808 ns | 0.756 ns | 0.85 | 0.01 | 0.0267 | - | 224 B |
| | | | | | | | | | |
| ToArray | 10 | 137.20 ns | 2.766 ns | 2.452 ns | 1.00 | 0.00 | 0.0372 | - | 312 B |
| ToList | 10 | 123.05 ns | 2.198 ns | 1.949 ns | 0.90 | 0.01 | 0.0372 | - | 312 B |
| | | | | | | | | | |
| ToArray | 42 | 398.25 ns | 6.583 ns | 5.836 ns | 1.00 | 0.00 | 0.0877 | - | 736 B |
| ToList | 42 | 352.04 ns | 4.976 ns | 4.411 ns | 0.88 | 0.02 | 0.0887 | - | 744 B |
| | | | | | | | | | |
| ToArray | 100 | 730.80 ns | 6.501 ns | 6.081 ns | 1.00 | 0.00 | 0.1488 | - | 1,248 B |
| ToList | 100 | 705.49 ns | 9.947 ns | 9.305 ns | 0.97 | 0.01 | 0.1526 | - | 1,280 B |
| | | | | | | | | | |
| ToArray | 1337 | 8,023.57 ns | 147.388 ns | 137.867 ns | 1.00 | 0.00 | 1.6785 | 0.0458 | 14,056 B |
| ToList | 1337 | 7,980.27 ns | 138.469 ns | 122.749 ns | 1.00 | 0.02 | 1.9989 | 0.1221 | 16,736 B |
| | | | | | | | | | |
| ToArray | 10000 | 57,037.19 ns | 510.492 ns | 452.538 ns | 1.00 | 0.00 | 12.6343 | 1.7700 | 106,280 B |
| ToList | 10000 | 57,728.15 ns | 583.353 ns | 517.127 ns | 1.01 | 0.01 | 15.5640 | 5.1270 | 131,496 B |作为参考,下面是原来的答案,不幸的是,它只在非常特殊的情况下执行了基准测试,避免了中间调整大小和复制操作。
原始答案:
现在是2020年,每个人都在使用.NET Core3.1,所以我决定使用Benchmark.NET运行一些基准测试。
TL;DR: ToArray()具有更好的性能,如果您不打算对集合进行变异,那么它可以更好地传达意图。
编辑:从注释中可以看出,这些基准可能不是指示性的,因为Enumerable.Range(...)返回一个包含序列大小信息的IEnumerable<T>,该信息随后被ToArray()用于优化,预先分配一个大小正确的数组。考虑手动测试性能,以确定确切的场景。
[MemoryDiagnoser]
public class Benchmarks
{
[Params(0, 1, 6, 10, 39, 100, 666, 1000, 1337, 10000)]
public int Count { get; set; }
public IEnumerable<int> Items => Enumerable.Range(0, Count);
[Benchmark(Description = "ToArray()", Baseline = true)]
public int[] ToArray() => Items.ToArray();
[Benchmark(Description = "ToList()")]
public List<int> ToList() => Items.ToList();
public static void Main() => BenchmarkRunner.Run<Benchmarks>();
}研究结果如下:
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.14393.3443 (1607/AnniversaryUpdate/Redstone1)
Intel Core i5-4460 CPU 3.20GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
Frequency=3124994 Hz, Resolution=320.0006 ns, Timer=TSC
.NET Core SDK=3.1.100
[Host] : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
DefaultJob : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
| Method | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------- |------ |--------------:|------------:|------------:|--------------:|------:|--------:|--------:|------:|------:|----------:|
| ToArray() | 0 | 7.357 ns | 0.2096 ns | 0.1960 ns | 7.323 ns | 1.00 | 0.00 | - | - | - | - |
| ToList() | 0 | 13.174 ns | 0.2094 ns | 0.1958 ns | 13.084 ns | 1.79 | 0.05 | 0.0102 | - | - | 32 B |
| | | | | | | | | | | | |
| ToArray() | 1 | 23.917 ns | 0.4999 ns | 0.4676 ns | 23.954 ns | 1.00 | 0.00 | 0.0229 | - | - | 72 B |
| ToList() | 1 | 33.867 ns | 0.7350 ns | 0.6876 ns | 34.013 ns | 1.42 | 0.04 | 0.0331 | - | - | 104 B |
| | | | | | | | | | | | |
| ToArray() | 6 | 28.242 ns | 0.5071 ns | 0.4234 ns | 28.196 ns | 1.00 | 0.00 | 0.0280 | - | - | 88 B |
| ToList() | 6 | 43.516 ns | 0.9448 ns | 1.1949 ns | 42.896 ns | 1.56 | 0.06 | 0.0382 | - | - | 120 B |
| | | | | | | | | | | | |
| ToArray() | 10 | 31.636 ns | 0.5408 ns | 0.4516 ns | 31.657 ns | 1.00 | 0.00 | 0.0331 | - | - | 104 B |
| ToList() | 10 | 53.870 ns | 1.2988 ns | 2.2403 ns | 53.415 ns | 1.77 | 0.07 | 0.0433 | - | - | 136 B |
| | | | | | | | | | | | |
| ToArray() | 39 | 58.896 ns | 0.9441 ns | 0.8369 ns | 58.548 ns | 1.00 | 0.00 | 0.0713 | - | - | 224 B |
| ToList() | 39 | 138.054 ns | 2.8185 ns | 3.2458 ns | 138.937 ns | 2.35 | 0.08 | 0.0815 | - | - | 256 B |
| | | | | | | | | | | | |
| ToArray() | 100 | 119.167 ns | 1.6195 ns | 1.4357 ns | 119.120 ns | 1.00 | 0.00 | 0.1478 | - | - | 464 B |
| ToList() | 100 | 274.053 ns | 5.1073 ns | 4.7774 ns | 272.242 ns | 2.30 | 0.06 | 0.1578 | - | - | 496 B |
| | | | | | | | | | | | |
| ToArray() | 666 | 569.920 ns | 11.4496 ns | 11.2450 ns | 571.647 ns | 1.00 | 0.00 | 0.8688 | - | - | 2728 B |
| ToList() | 666 | 1,621.752 ns | 17.1176 ns | 16.0118 ns | 1,623.566 ns | 2.85 | 0.05 | 0.8793 | - | - | 2760 B |
| | | | | | | | | | | | |
| ToArray() | 1000 | 796.705 ns | 16.7091 ns | 19.8910 ns | 796.610 ns | 1.00 | 0.00 | 1.2951 | - | - | 4064 B |
| ToList() | 1000 | 2,453.110 ns | 48.1121 ns | 65.8563 ns | 2,460.190 ns | 3.09 | 0.10 | 1.3046 | - | - | 4096 B |
| | | | | | | | | | | | |
| ToArray() | 1337 | 1,057.983 ns | 20.9810 ns | 41.4145 ns | 1,041.028 ns | 1.00 | 0.00 | 1.7223 | - | - | 5416 B |
| ToList() | 1337 | 3,217.550 ns | 62.3777 ns | 61.2633 ns | 3,203.928 ns | 2.98 | 0.13 | 1.7357 | - | - | 5448 B |
| | | | | | | | | | | | |
| ToArray() | 10000 | 7,309.844 ns | 160.0343 ns | 141.8662 ns | 7,279.387 ns | 1.00 | 0.00 | 12.6572 | - | - | 40064 B |
| ToList() | 10000 | 23,858.032 ns | 389.6592 ns | 364.4874 ns | 23,759.001 ns | 3.26 | 0.08 | 12.6343 | - | - | 40096 B |
// * Hints *
Outliers
Benchmarks.ToArray(): Default -> 2 outliers were removed (35.20 ns, 35.29 ns)
Benchmarks.ToArray(): Default -> 2 outliers were removed (38.51 ns, 38.88 ns)
Benchmarks.ToList(): Default -> 1 outlier was removed (64.69 ns)
Benchmarks.ToArray(): Default -> 1 outlier was removed (67.02 ns)
Benchmarks.ToArray(): Default -> 1 outlier was removed (130.08 ns)
Benchmarks.ToArray(): Default -> 1 outlier was detected (541.82 ns)
Benchmarks.ToArray(): Default -> 1 outlier was removed (7.82 us)
// * Legends *
Count : Value of the 'Count' parameter
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
Median : Value separating the higher half of all measurements (50th percentile)
Ratio : Mean of the ratio distribution ([Current]/[Baseline])
RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline])
Gen 0 : GC Generation 0 collects per 1000 operations
Gen 1 : GC Generation 1 collects per 1000 operations
Gen 2 : GC Generation 2 collects per 1000 operations
Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
1 ns : 1 Nanosecond (0.000000001 sec)https://stackoverflow.com/questions/1105990
复制相似问题