在LINQ查询中调用ToList()或ToArray()更好吗?


518

我经常遇到我想在声明它的地方评估一个查询的情况。这通常是因为我需要对其进行多次迭代,并且计算起来很昂贵。例如:

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()吗-安全又有保障,因为不会为内存分配两次?


10
如果您想了解.NET幕后发生的事情,我真的建议您使用.NET Reflector
David Hedlund 2009年

32
@DavidHedlund我推荐.net源代码
Gqqnbig 2014年

1
我不同意即使存在重要关系,stackoverflow.com / questions / 6750447 / c-toarray-performance是该问题的副本。内存使用(此问题)和性能(其他问题)两者都是有趣且重要的考虑因素。可以分别描述它们,但是两者都应考虑选择一个而不是另一个的决定。对于这个问题或其他问题,我不能推荐任何一个全面的答案。有几个答案可以一起提供关于如何选择一个而不是另一个的相当完整的讨论。
史蒂夫

1
@Gqqnbig-有史以来最有用的评论!谢谢:-)
马克·库珀

Answers:


365

除非您仅需要一个数组来满足其他约束,否则应使用ToList。在大多数情况下,ToArray将分配比更大的内存ToList

两者都使用数组进行存储,但ToList约束更灵活。它需要数组至少与集合中的元素数量一样大。如果数组较大,那不是问题。但是,ToArray需要将数组的大小精确地调整为元素数。

为了满足这一约束,ToArray分配的数量通常比分配的多ToList。一旦具有足够大的数组,它就会分配一个大小正确的数组,并将元素复制回该数组中。唯一可以避免这种情况的时间是,数组的增长算法恰好与需要存储的元素数量一致(绝对是少数)。

编辑

有几个人问我有关List<T>值中包含多余的未使用内存的后果。

这是一个有效的担忧。如果创建的集合寿命很长,在创建之后再也不会修改,并且很有可能进入Gen2堆,那么最好预先分配额外的资源ToArray

总的来说,尽管我发现这是罕见的情况。看到很多ToArray调用立即传递给其他短暂的内存使用情况更为常见,在这种情况下ToList显然更好。

这里的关键是剖析,剖析然后再剖析更多。


14
另一方面,分配给创建数组的繁琐工作的额外内存是否不符合垃圾回收的条件,而List的额外开销仍将保留?我说保持简单。如果您需要添加或删除元素,那么可以使用一个工具。如果您不这样做,那么可以使用另一种工具。使用有意义的一种。如果以后,你会发现内存和性能的问题,这就是它,改变它。
Anthony Pegram

1
@AnthonyPegram是的,这是一个有效的考虑因素。如果该值用于长期存储,不会被修改,并且有可能进入第二代,那么与污染第二代堆相比,您现在最好支付额外的分配。IME虽然我很少看到。看到ToArray立即传递给另一个短暂的LINQ查询是很常见的。
JaredPar

2
@AnthonyPegram我更新了我的回答,以包括讨论的这一方面
JaredPar

8
@JaredPar我不明白ToArray如果需要确切的位置大小(ToList<>显然有自动备用位置),如何分配更多内存。(自动增加)
Royi Namir 2014年

5
@RoyiNamir,因为ToArray首先执行带有开销的ToList样式分配,然后执行附加的精确大小分配。
Timbo 2014年

169

由于List<T>实现为动态大小的数组,因此性能差异将不明显。调用ToArray()(使用内部Buffer<T>类来增长数组)或ToList()(调用List<T>(IEnumerable<T>)构造函数)将最终成为将它们放入数组并增长数组直到适合所有数组的问题。

如果您希望对此事实进行具体确认,请在Reflector中检查相关方法的实现-您会发现它们简化为几乎相同的代码。


2
我遇到的一个有趣的事实是,对于通过在投影中使用通过组联接定义的组而导致的相关查询,Linq to SQL添加了另一个子查询来检索该组的计数。我假设这意味着在这些情况下,将在检索项目之前知道集合的大小,因此可以直接创建大小正确的数组,这将在实现结果的同时节省处理和内存资源。
jpierson

133
如果预先知道计数,则性能相同。但是,如果事先不知道Count,则ToArray()和之间的唯一区别ToList()是,前者必须裁剪多余的部分,这涉及复制整个数组,而后者不会裁剪多余的部分,但平均使用25 %的内存。仅当数据类型较大时,这才有意义struct。值得深思。
Scott Rippey 2010年

9
@EldritchConundrum 25%来自此逻辑:如果项数未知,则调用ToListToArray将通过创建一个小缓冲区开始。当该缓冲区已满时,它将使缓冲区的容量增加一倍并继续。由于容量总是加倍,因此未使用的缓冲区将始终在0%到50%之间。
Scott Rippey

2
@ScottRippey我只是从IEnumerable源中查找了新List的源,它检查IEnumerable是否为ICollection,如果是,则通过从Count属性分配一个具有确切大小的数组开始,因此ToList()肯定会更快的情况。一个完整的答案可能包括这个事实,尽管我认为这不是最常见的情况。
AndyClaw 2014年

3
@AndyClaw ListBuffer都将检查ICollection,在这种情况下,性能将相同。
Scott Rippey 2014年

54

(七年后...)

其他两个(好的)答案集中在将要发生的微观性能差异上。

这篇文章只是补充,以提及array()产生的与a返回的语义之间存在的语义差异IEnumerator<T>T[]List<T>

通过示例最好地说明:

IList<int> source = Enumerable.Range(1, 10).ToArray();  // try changing to .ToList()

foreach (var x in source)
{
  if (x == 5)
    source[8] *= 100;
  Console.WriteLine(x);
}

上面的代码将毫无例外地运行并产生输出:

1个
2
3
4
5
6
7
8
900
10

这表明由IEnumarator<int>返回的int[]不能跟踪自创建枚举器以来是否已修改数组。

请注意,我将局部变量声明sourceIList<int>。通过这种方式,我确保C#编译器不会将foreach语句优化为等效于for (var idx = 0; idx < source.Length; idx++) { /* ... */ }循环的内容。如果我改用C#编译器,可能会这样做var source = ...;。在我当前的.NET Framework版本中,此处使用的实际枚举器是非公共引用类型,System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]但是当然这是实现细节。

现在,如果更改.ToArray().ToList(),则只能得到:

1个
2
3
4
5

接着是一句System.InvalidOperationException鼓吹的话:

集合已修改;枚举操作可能无法执行。

在这种情况下,底层的枚举数是公共可变值类型System.Collections.Generic.List`1+Enumerator[System.Int32]IEnumerator<int>在这种情况下,由于使用,所以装在了一个盒子内IList<int>)。

总之,List<T>清单生成的枚举数会跟踪列表在枚举期间是否发生更改,而由清单生成的枚举数T[]不会发生变化。因此,在.ToList()和之间选择时要考虑这种差异.ToArray()

人们往往添加一个额外的 .ToArray().ToList()规避集合,保持它是否在一个枚举的终身修改轨道。

(如果有人想知道如何List<>跟踪上收集是否被修改,有一个私人领域_version在这个类,这是改变的每次List<>更新。)


28

我同意@mquander所说的性能差异应该很小。但是,我想对它进行基准测试以确保确定,所以我做到了-而且那是微不足道的。

Testing with List<T> source:
ToArray time: 1934 ms (0.01934 ms/call), memory used: 4021 bytes/array
ToList  time: 1902 ms (0.01902 ms/call), memory used: 4045 bytes/List

Testing with array source:
ToArray time: 1957 ms (0.01957 ms/call), memory used: 4021 bytes/array
ToList  time: 2022 ms (0.02022 ms/call), memory used: 4045 bytes/List

每个源数组/列表具有1000个元素。因此,您可以看到时间和内存上的差异都可以忽略不计。

我的结论是:您最好使用ToList(),因为a List<T>提供的功能比数组更多,除非真正需要几个字节的内存。


1
我想知道如果您使用大struct而不是原始类型或类,那么结果是否会有所不同。
Scott Rippey

12
List <T> .ToList ???? 什么意思 最好不要给它IEnumerable,因为它不实现ICollection接口。
Grigory

8
我想确保只测量ToListor 的时间,ToArray而不要测量任何时间IEnumerable。List <T> .ToList()仍然创建一个新的List <T>-它不仅仅是“返回此”。
EMP

23
-1 当提供参数时,ToArray()和的行为ToList()相差太大ICollection<T>-它们仅执行一次分配和一次复制操作。无论List<T>Array落实ICollection<T>,所以你的基准是不是在所有有效。
Mohammad Dehghan 2014年

1
对于感兴趣的任何人,我都会发布自己的基准测试作为单独的答案。它用于.Select(i => i)避免ICollection<T>实现问题,并包括一个控制组,以首先查看在源上进行迭代所需的时间IEnumerable<>
StriplingWarrior

19

ToList()通常是首选的,如果您使用它IEnumerable<T>(例如,从ORM)。如果开头的序列长度未知,则ToArray()创建动态长度集合(如List),然后将其转换为数组,这会花费额外的时间。


26
我决定在这种情况下,可读性胜过性能。我现在仅在希望继续添加元素时才使用ToList。在所有其他情况下(大多数情况下),我使用ToArray。但是感谢您的投入!
Frank Krueger'2

5
在ILSpy中,Enumerable.ToArray()调用new Buffer<TSource>(source).ToArray()。在Buffer构造函数中,如果源实现ICollection,则它将调用source.CopyTo(items,0),然后.ToArray()直接返回内部项目数组。因此,在这种情况下,没有任何转换会花费额外的时间。如果源代码未实现ICollection,则ToArray将生成数组副本,以便从数组末尾修剪多余的未使用位置,如上面的Scott Rippey的评论所述。
BrandonAGr

19

内存将始终分配两次-或接近于此。由于无法调整数组大小,因此这两种方法都将使用某种机制来收集不断增长的集合中的数据。(嗯,列表本身就是一个不断增长的集合。)

列表使用阵列作为内部存储,并在需要时将容量增加一倍。这意味着平均有2/3的项目至少已重新分配一次,一半的项目至少重新分配了两次,一半的项目至少三次了,依此类推。这意味着每个项目平均已被重新分配了1.3倍,这并不是很多开销。

还要记住,如果要收集字符串,则集合本身仅包含对字符串的引用,而不会重新分配字符串本身。


这可能是一个无知的问题,但是您概述的2 / 3、1 / 3、1 / 6逻辑不是假定List的数组可以在适当的位置扩展吗?也就是说,数组末尾有可用空间,因此不需要移动现有分配?

@JonofAllTrades:不,数组永远不会在适当的地方扩展,.NET中的内存管理根本无法做到这一点。如果将其扩展到位,则无需重新分配项目。
Guffa

嗯,我知道了:未分配的项目不必这样做,因为它们已在最终分配中。先前分配中分配的所有项目均已移动,但是由于数组长度的对数增加,这是可计算的分数。感谢您的澄清!

19

现在是2020年,每个人都在使用.NET Core 3.1,所以我决定在Benchmark.NET上运行一些基准测试。

TL; DR:如果您不打算对集合进行突变,则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)

1
如果您不打算对集合进行更改,我认为可以ToImmutableArray()(从System.Collections.Immutable包中)更好地显示其意图
Arturo

@ArturoTorresSánchez是的,但是如果集合没有在方法之外公开,我将只使用数组。
Tyrrrz

2
谢谢你 选择的答案仅仅是一个论点,并假设该论点之后的结果。要科学地做到这一点并作为奖励知道有多少区别,只有一种真正的方法可以知道。
乔纳斯(Jonas)

15

编辑:此答案的最后一部分无效。但是,其余信息仍然有用,因此我将其保留。

我知道这是一篇过时的文章,但是在提出相同的问题并进行了一些研究之后,我发现了一些有趣的事情可能值得分享。

首先,我同意@mquander及其回答。他说正确的是,在性能方面,两者是相同的。

但是,我一直在使用Reflector来查看System.Linq.Enumerable扩展名称空间中的方法,并且我注意到了一个非常常见的优化。
只要有可能,将IEnumerable<T>源强制转换为IList<T>ICollection<T>优化方法。例如,查看ElementAt(int)

有趣的是,Microsoft选择仅针对进行优化IList<T>,而不针对IList。看起来Microsoft更喜欢使用该IList<T>界面。

System.Array仅实现IList,因此不会从这些扩展优化中受益。
因此,我认为最佳实践是使用该.ToList()方法。
如果您使用任何扩展方法,或将列表传递给另一种方法,则有可能针对进行了优化IList<T>


16
我做了测试,发现了一些令人惊讶的东西。数组确实实现了IList <T>!使用Reflector分析System.Array仅揭示了IList,ICollection,IEnumerable的继承链,但是使用运行时反射,我发现string []具有IList,ICollection,IEnumerable,IList <string>,ICollection <string的继承链。 >,IEnumerable <字符串>。因此,我没有比@mquander更好的答案了!
斯科特·里皮

@ScottRippey是的。您注意到的奇怪观察实际上是“ hack”的一部分-对于“ fixed-size”和类似的属性(根据您如何投射它,有些不一致)也有一些相当奇怪的含义。.net源代码中有一些相当大的注释涉及此主题。抱歉,没有链接,但是如果我没记错的话,在数组类内部很容易找到它。(还有一个大的SO问题,讨论不一致之处....某处...> __>)
AnorZaken 2015年

仅供参考,@ ScottRippey我发现这个答案与您的评论有关:stackoverflow.com/a/4482567/2063755
David Klempfner,

14

我发现人们在这里没有做过的其他基准测试,所以这是我的缺点。如果您发现我的方法有问题,请告诉我。

/* This is a benchmarking template I use in LINQPad when I want to do a
 * quick performance test. Just give it a couple of actions to test and
 * it will give you a pretty good idea of how long they take compared
 * to one another. It's not perfect: You can expect a 3% error margin
 * under ideal circumstances. But if you're not going to improve
 * performance by more than 3%, you probably don't care anyway.*/
void Main()
{
    // Enter setup code here
    var values = Enumerable.Range(1, 100000)
        .Select(i => i.ToString())
        .ToArray()
        .Select(i => i);
    values.GetType().Dump();
    var actions = new[]
    {
        new TimedAction("ToList", () =>
        {
            values.ToList();
        }),
        new TimedAction("ToArray", () =>
        {
            values.ToArray();
        }),
        new TimedAction("Control", () =>
        {
            foreach (var element in values)
            {
                // do nothing
            }
        }),
        // Add tests as desired
    };
    const int TimesToRun = 1000; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}


#region timer helper methods
// Define other methods and classes here
public void TimeActions(int iterations, params TimedAction[] actions)
{
    Stopwatch s = new Stopwatch();
    int length = actions.Length;
    var results = new ActionResult[actions.Length];
    // Perform the actions in their initial order.
    for (int i = 0; i < length; i++)
    {
        var action = actions[i];
        var result = results[i] = new ActionResult { Message = action.Message };
        // Do a dry run to get things ramped up/cached
        result.DryRun1 = s.Time(action.Action, 10);
        result.FullRun1 = s.Time(action.Action, iterations);
    }
    // Perform the actions in reverse order.
    for (int i = length - 1; i >= 0; i--)
    {
        var action = actions[i];
        var result = results[i];
        // Do a dry run to get things ramped up/cached
        result.DryRun2 = s.Time(action.Action, 10);
        result.FullRun2 = s.Time(action.Action, iterations);
    }
    results.Dump();
}

public class ActionResult
{
    public string Message { get; set; }
    public double DryRun1 { get; set; }
    public double DryRun2 { get; set; }
    public double FullRun1 { get; set; }
    public double FullRun2 { get; set; }
}

public class TimedAction
{
    public TimedAction(string message, Action action)
    {
        Message = message;
        Action = action;
    }
    public string Message { get; private set; }
    public Action Action { get; private set; }
}

public static class StopwatchExtensions
{
    public static double Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.Elapsed.TotalMilliseconds;
    }
}
#endregion

您可以在此处下载LINQPad脚本

结果: ToArray与ToList的性能

通过调整上面的代码,您会发现:

  1. 处理较小的数组时,差异不那么明显。 迭代次数更多,但数组更小
  2. 当处理ints而不是strings 时,差异不那么显着。
  3. 通常,使用大号structs代替strings会花费更多时间,但实际上并没有太大改变比率。

这与最受好评的答案的结论一致:

  1. 除非您的代码经常产生许多大型数据列表,否则您不太可能注意到性能差异。(每个创建100K字符串的1000个列表时,仅相差200毫秒。)
  2. ToList() 始终运行得更快,如果您不打算长期坚持结果,那将是一个更好的选择。

更新资料

@JonHanna指出,根据实现的不同,或实现Select可能会提前预测结果集合的大小。目前,用上述代码替换上面的代码会产生非常相似的结果,并且无论.NET实现如何,这样做的可能性更大。ToList()ToArray().Select(i => i)Where(i => true)

使用Where而不是Select进行基准测试


在.NET Core中,这两种情况都应该比netfx更好,因为它会意识到大小会变大,100000并使用它来优化ToList()and和ToArray(),并且ToArray()由于它不需要收缩操作而变得更轻巧否则,这是一个ToList()优势。问题中的示例仍然会失败,因为Where无法进行此类尺寸预测。
乔恩·汉纳

@JonHanna:感谢您的快速反馈。我不知道.NET Core正在进行优化。这很酷。在我的代码中,.Select(i => i)可以用.Where(i => true)进行更正。
脱衣战士

是的,这将停止影响corefx的优化。ToArray()如上所述,同时具有两个的幂(应该有优势)和一个不是的幂的大小,并比较结果可能会很有趣。
乔恩·汉纳

@JonHanna:有趣的是,在最佳情况下ToArray() 仍然会失败。有了Math.Pow(2, 15)元素,它就是(ToList:700ms,ToArray:900ms)。再添加一个元素会将其颠倒到(ToList:925,ToArray:1350)。我想知道ToArray即使已经是完美的大小,是否仍在复制数组?他们可能认为这是非常罕见的情况,因此不值得额外附加条件。
StriplingWarrior

即使在我们开始在corefx中对其进行优化之前,它也并没有按照精确的大小匹配进行复制,因此这种情况下,它得到的中断最多。
乔恩·汉纳 Jon Hanna)

12

您应该根据设计选择ToListToArray基于理想的选择来做出决定。如果您想要一个只能按索引迭代和访问的集合,请选择ToArray。如果您想要稍后在没有过多麻烦的情况下从集合中添加和删除的其他功能,请执行一个操作ToList(并不是您不能添加到数组中,但这通常不是正确的工具)。

如果性能很重要,您还应该考虑使用哪种操作会更快。实际上,您不会打电话ToList或进行ToArray一百万次,但可能会对获得的集合进行一百万次。在这方面[]比较好,因为List<>[]有一些开销。有关效率比较,请参见此线程:哪个效率更高:List <int>或int []

不久前在我自己的测试中,我发现 ToArray速度更快。而且我不确定测试的偏斜程度。但是,性能差异是如此微不足道,只有当您在循环中运行这些查询数百万次时,该差异才可以注意到。


2
是的-如果编译器知道您要遍历数组(而不是IEnumerable <>),则可以显着优化迭代。
RobSiklos

12

答案很晚,但我认为这对Google员工会有所帮助。

当使用linq创建它们时,它们都很烂。它们都实现相同的代码以在必要时调整缓冲区大小ToArray内部使用类进行转换IEnumerable<>,通过分配4个元素的数组为数组。如果这还不够,则通过创建一个新数组,将当前数组的大小加倍,然后将当前数组复制到该数组中,从而使数组大小加倍。最后,它将分配一个新的商品计数数组。如果查询返回129个元素,则ToArray将进行6次分配和内存复制操作以创建256个元素数组,然后返回另一个129个元素的数组。大大提高了内存效率。

ToList做同样的事情,但是它跳过了最后的分配,因为您将来可以添加项目。列表不在乎是从linq查询创建还是手动创建。

对于列表创建,列表在内存方面更好,而在cpu方面则更糟,因为列表是一种通用解决方案,每个操作都需要对.net内部数组范围进行检查之外再进行范围检查。

因此,如果您要遍历结果集太多次,则数组是好的选择,因为这意味着范围检查少于列表,并且编译器通常会优化数组以进行顺序访问。

如果在创建列表时指定容量参数,则列表的初始化分配会更好。在这种情况下,假设您知道结果的大小,它将仅分配一次数组。ToListlinq的值没有指定要提供的重载,因此我们必须创建扩展方法,该方法创建具有给定容量的列表,然后使用List<>.AddRange

为了完成这个答案,我必须写以下句子

  1. 最后,您可以使用ToArray或ToList,性能不会有太大差别(请参阅@EMP的答案)。
  2. 您正在使用C#。如果您需要性能,那么不必担心编写高性能代码,而不必担心编写性能不好的代码。
  3. 始终将x64定位为高性能代码。AFAIK,x64 JIT基于C ++编译器,并做了一些有趣的事情,例如尾递归优化。
  4. 使用4.5,您还可以享受配置文件引导的优化和多核JIT。
  5. 最后,您可以使用异步/等待模式来更快地处理它。

他们俩都烂吗?您是否有一个不需要冗余内存分配的备用想法?
2013年

在有疑问的情况下,是的,它们都是糟糕的,但是由于冗余的分配,仅此而已。为了减少冗余分配,可以使用链接列表,但要牺牲内存和迭代速度。归根结底,这就是我们要做的事,我们在取舍。创建一个容量为200(例如)的列表然后加载项目的另一个想法。这也将减少冗余,但是阵列总是更快,因此这是另一个权衡。
Erdogan Kurtur 2013年

创建200个清单?那可能避免调整大小,但是我在谈论使用的冗余内存。您无能为力,因为尚不了解大小可能。您已经可以在的构造函数中指定容量List<T>,但是当您不指定或不指定容量时,您将无能为力。
nawfal

2
内存中唯一的冗余数据是数组的内容,该内容是一个指针列表(在这种情况下)。一百万个64位指针占用多达8 MB的内存,与它们指向的一百万个对象相比,这算什么了。200只是一个数字,它有机会减少最多5次的调整大小调用次数。是的,我们无能为力。我们没有更好的选择。我没有更好的解决方案,但这并不意味着我不能说问题出在哪里。
2013年

1
嗯,最后就是画线的地方。我喜欢当前的实现。您的回答语气使我认为这是批评而不是问题所在 :)
nawfal

7

这是一个古老的问题-但为了偶然发现它的用户的利益,还有“替代”可枚举的替代方法-具有缓存和停止Linq语句的多个枚举的作用,这就是ToArray()和ToList()被大量使用,即使从不使用列表或数组的collection属性。

Memoize在RX / System.Interactive库中可用,并在此处进行了说明: 使用System.Interactive的更多LINQ

(摘自Bart De'Smet的博客,如果您经常使用Linq to Objects ,则强烈建议阅读该文章)


4

一种选择是添加您自己的扩展方法,该方法返回readonly ICollection<T>。这可能比使用ToListToArray当您既不想使用数组/列表的索引属性,也不希望从列表中添加/删除时更好。

public static class EnumerableExtension
{
    /// <summary>
    /// Causes immediate evaluation of the linq but only if required.
    /// As it returns a readonly ICollection, is better than using ToList or ToArray
    /// when you do not want to use the indexing properties of an IList, or add to the collection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumerable"></param>
    /// <returns>Readonly collection</returns>
    public static ICollection<T> Evaluate<T>(this IEnumerable<T> enumerable)
    {
        //if it's already a readonly collection, use it
        var collection = enumerable as ICollection<T>;
        if ((collection != null) && collection.IsReadOnly)
        {
            return collection;
        }
        //or make a new collection
        return enumerable.ToList().AsReadOnly();
    }
}

单元测试:

[TestClass]
public sealed class EvaluateLinqTests
{
    [TestMethod]
    public void EvalTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResult = list.Select(i => i);
        var linqResultEvaluated = list.Select(i => i).Evaluate();
        list.Clear();
        Assert.AreEqual(0, linqResult.Count());
        //even though we have cleared the underlying list, the evaluated list does not change
        Assert.AreEqual(3, linqResultEvaluated.Count());
    }

    [TestMethod]
    public void DoesNotSaveCreatingListWhenHasListTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //list is not readonly, so we expect a new list
        Assert.AreNotSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasReadonlyListTest()
    {
        var list = new List<int> {1, 2, 3}.AsReadOnly();
        var linqResultEvaluated = list.Evaluate();
        //list is readonly, so we don't expect a new list
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasArrayTest()
    {
        var list = new[] {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //arrays are readonly (wrt ICollection<T> interface), so we don't expect a new object
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantAddToResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Add(4);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantRemoveFromResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Remove(1);
    }
}

值得注意的是,只读收集合同仅规定对象的用户不得对其进行修改,但是如果所有者保留提供可变接口的引用,则所有者仍可以进行修改。对于保证底层结构不会改变的接口,请查看不可变的集合。关于为什么不变或只读或普通读写集合是更好还是更坏的原因,人们需要一个参考点进行比较。没有最终答案(否则我们不必选择)。
tne 2014年

@tne注意我会在AsReadOnly之前进行Tolist,因此没有对基础可变对象的引用。
weston 2014年

您是完全正确的,这可能是在不可变集合进入BCL之前做事情的最好方法(我看到第一个Beta发布于您回答之后一个月)。
tne 2014年

存在不可变集合以确保线程安全,线程可以假定它不会更改,如果不更改,则会创建一个新版本,而不是与读者争夺并在使用时对其进行更改。这样,没有人需要获取锁。
doug65536 '16

4

ToListAsync<T>() 是首选。

在Entity Framework 6中,这两种方法最终都调用相同的内部方法,但最后ToArrayAsync<T>()调用list.ToArray(),实现为

T[] array = new T[_size];
Array.Copy(_items, 0, array, 0, _size);
return array;

因此ToArrayAsync<T>()有一些开销,因此ToListAsync<T>()是首选。


1
实际上,这就是我一直在寻找的答案,EF如何做到这一点。我很好奇EF Core中的情况。
Shimmy Weitzhandler

3

老问题总是新问题。

根据System.Linq.Enumerable的来源,ToList仅返回a new List(source),而ToArray使用a new Buffer<T>(source).ToArray()返回a T[]

关于内存分配:

IEnumerable<T>唯一对象上运行时,ToArray分配内存的时间要比分配对象多一倍ToList。但是在大多数情况下,您不必关心它,因为GC会在需要时进行垃圾回收。

关于运行时高效:

那些对这个问题有疑问的人可以在自己的计算机上运行以下代码,您将得到答案。

class PersonC
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

struct PersonS
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

class PersonT<T> : IEnumerable<T>
{
    private List<T> items;
    public PersonT(IEnumerable<T> init)
    {
        items = new List<T>(init);
    }

    public IEnumerator<T> GetEnumerator() => items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator();
}

private IEnumerable<PersonC> C(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonC
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private IEnumerable<PersonS> S(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonS
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private void MakeLog(string test, List<long> log) =>
    Console.WriteLine("{0} {1} ms -> [{2}]",
        test,
        log.Average(),
        string.Join(", ", log)
    );

private void Test1(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    MakeLog("C.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test2(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC1 = new PersonT<PersonC>(C(count));
    var dataS1 = new PersonT<PersonS>(S(count));

    MakeLog("C1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test3(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC2 = (ICollection<PersonC>) new List<PersonC>(C(count));
    var dataS2 = (ICollection<PersonS>) new List<PersonS>(S(count));

    MakeLog("C2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void TestMain()
{
    const int times = 100;
    const int count = 1_000_000 + 1;
    Test1(times, count);
    Test2(times, count);
    Test3(times, count);
}

我在计算机上得到了以下结果:

第一组:

C.ToList 761.79 ms -> [775, 755, 759, 759, 756, 759, 765, 750, 757, 762, 759, 754, 757, 753, 763, 753, 759, 756, 768, 754, 763, 757, 757, 777, 780, 758, 754, 758, 762, 754, 758, 757, 763, 758, 760, 754, 761, 755, 764, 847, 952, 755, 747, 763, 760, 758, 754, 763, 761, 758, 750, 764, 757, 763, 762, 756, 753, 759, 759, 757, 758, 779, 765, 760, 760, 756, 760, 756, 755, 764, 759, 753, 757, 760, 752, 764, 758, 760, 758, 760, 755, 761, 751, 753, 761, 762, 761, 758, 759, 752, 765, 756, 760, 755, 757, 753, 760, 751, 755, 779]
C.ToArray 782.56 ms -> [783, 774, 771, 771, 773, 774, 775, 775, 772, 770, 771, 774, 771, 1023, 975, 772, 767, 776, 771, 779, 772, 779, 775, 771, 775, 773, 775, 771, 765, 774, 770, 781, 772, 771, 781, 762, 817, 770, 775, 779, 769, 774, 763, 775, 777, 769, 777, 772, 775, 778, 775, 771, 770, 774, 772, 769, 772, 769, 774, 775, 768, 775, 769, 774, 771, 776, 774, 773, 778, 769, 778, 767, 770, 787, 783, 779, 771, 768, 805, 780, 779, 767, 773, 771, 773, 785, 1044, 853, 775, 774, 775, 771, 770, 769, 770, 776, 770, 780, 821, 770]
S.ToList 704.2 ms -> [687, 702, 709, 691, 694, 710, 696, 698, 700, 694, 701, 719, 706, 694, 702, 699, 699, 703, 704, 701, 703, 705, 697, 707, 691, 697, 707, 692, 721, 698, 695, 700, 704, 700, 701, 710, 700, 705, 697, 711, 694, 700, 695, 698, 701, 692, 696, 702, 690, 699, 708, 700, 703, 714, 701, 697, 700, 699, 694, 701, 697, 696, 699, 694, 709, 1068, 690, 706, 699, 699, 695, 708, 695, 704, 704, 700, 695, 704, 695, 696, 702, 700, 710, 708, 693, 697, 702, 694, 700, 706, 699, 695, 706, 714, 704, 700, 695, 697, 707, 704]
S.ToArray 742.5 ms -> [742, 743, 733, 745, 741, 724, 738, 745, 728, 732, 740, 727, 739, 740, 726, 744, 758, 732, 744, 745, 730, 739, 738, 723, 745, 757, 729, 741, 736, 724, 744, 756, 739, 766, 737, 725, 741, 742, 736, 748, 742, 721, 746, 1043, 806, 747, 731, 727, 742, 742, 726, 738, 746, 727, 739, 743, 730, 744, 753, 741, 739, 746, 728, 740, 744, 734, 734, 738, 731, 747, 736, 731, 765, 735, 726, 740, 743, 730, 746, 742, 725, 731, 757, 734, 738, 741, 732, 747, 744, 721, 742, 741, 727, 745, 740, 730, 747, 760, 737, 740]

C1.ToList 32.34 ms -> [35, 31, 31, 31, 32, 31, 31, 31, 31, 31, 31, 31, 31, 31, 33, 32, 31, 31, 31, 31, 30, 32, 31, 31, 31, 31, 32, 30, 31, 31, 31, 30, 32, 31, 31, 31, 36, 31, 31, 31, 32, 30, 31, 32, 31, 31, 31, 31, 31, 32, 31, 31, 31, 31, 33, 32, 31, 32, 31, 31, 33, 31, 31, 31, 31, 31, 32, 31, 32, 31, 34, 38, 68, 42, 79, 33, 31, 31, 31, 31, 31, 30, 30, 30, 30, 31, 31, 31, 31, 32, 31, 32, 31, 31, 31, 32, 33, 33, 31, 31]
C1.ToArray 56.32 ms -> [57, 56, 59, 54, 54, 55, 56, 57, 54, 54, 55, 55, 57, 56, 59, 57, 56, 58, 56, 56, 54, 56, 57, 55, 55, 55, 57, 58, 57, 58, 55, 55, 56, 55, 57, 56, 56, 59, 56, 56, 56, 56, 58, 56, 57, 56, 56, 57, 56, 55, 56, 56, 56, 59, 56, 56, 56, 55, 55, 54, 55, 54, 57, 56, 56, 56, 55, 55, 56, 56, 56, 59, 56, 56, 57, 56, 57, 56, 56, 56, 56, 62, 55, 56, 56, 56, 69, 57, 58, 56, 57, 58, 56, 57, 56, 56, 56, 56, 56, 56]
S1.ToList 88.69 ms -> [96, 90, 90, 89, 91, 88, 89, 90, 96, 89, 89, 89, 90, 90, 90, 89, 90, 90, 89, 90, 89, 91, 89, 91, 89, 91, 89, 90, 90, 89, 87, 88, 87, 88, 87, 87, 87, 87, 88, 88, 87, 87, 89, 87, 87, 87, 91, 88, 87, 86, 89, 87, 90, 89, 89, 90, 89, 87, 87, 87, 86, 87, 88, 90, 88, 87, 87, 92, 87, 87, 88, 88, 88, 86, 86, 87, 88, 87, 87, 87, 89, 87, 89, 87, 90, 89, 89, 89, 91, 89, 90, 89, 90, 88, 90, 90, 90, 88, 89, 89]
S1.ToArray 143.26 ms -> [130, 129, 130, 131, 133, 130, 131, 130, 135, 137, 130, 136, 132, 131, 130, 131, 132, 130, 132, 136, 130, 131, 157, 153, 194, 364, 176, 189, 203, 194, 189, 192, 183, 140, 142, 147, 145, 134, 159, 158, 142, 167, 130, 143, 145, 144, 160, 154, 156, 153, 153, 164, 142, 145, 137, 134, 145, 143, 142, 135, 133, 133, 135, 134, 134, 139, 139, 133, 134, 141, 133, 132, 133, 132, 133, 131, 135, 132, 133, 132, 128, 128, 130, 132, 129, 129, 129, 129, 129, 128, 134, 129, 129, 129, 129, 128, 128, 137, 130, 131]

C2.ToList 3.25 ms -> [5, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3]
C2.ToArray 3.37 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 4, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3]
S2.ToList 37.72 ms -> [38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 40, 38, 38, 39, 39, 38, 38, 38, 38, 37, 37, 37, 37, 39, 37, 37, 39, 38, 37, 37, 37, 37, 39, 38, 37, 37, 38, 37, 38, 37, 37, 38, 37, 37, 37, 38, 37, 37, 36, 37, 38, 37, 39, 37, 39, 38, 37, 38, 38, 38, 38, 38, 38, 37, 38, 38, 38, 38, 38, 37, 38, 37, 37, 38, 37, 37, 39, 41, 37, 38, 38, 37, 37, 37, 37, 38, 37, 37, 37, 40, 37, 37, 37, 37, 39, 38]
S2.ToArray 38.86 ms -> [39, 37, 39, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 38, 38, 38, 39, 37, 38, 38, 38, 38, 38, 37, 37, 38, 37, 37, 38, 38, 40, 38, 38, 38, 38, 38, 39, 38, 38, 39, 38, 38, 39, 38, 38, 40, 38, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 39, 37, 38, 38, 39, 71, 78, 37, 37, 37, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 38, 38, 38]

第二组:

C.ToList 756.81 ms
C.ToArray 774.21 ms
S.ToList 709.7 ms
S.ToArray 753.51 ms

C1.ToList 32.06 ms
C1.ToArray 56.58 ms
S1.ToList 89.43 ms
S1.ToArray 132.85 ms

C2.ToList 3.45 ms
C2.ToArray 3.36 ms
S2.ToList 41.43 ms
S2.ToArray 40.84 ms

第三组:

C.ToList 756.64 ms
C.ToArray 771.56 ms
S.ToList 705.42 ms
S.ToArray 749.59 ms

C1.ToList 31.45 ms
C1.ToArray 57.03 ms
S1.ToList 91.26 ms
S1.ToArray 129.77 ms

C2.ToList 3.26 ms
C2.ToArray 3.29 ms
S2.ToList 41.57 ms
S2.ToArray 40.69 ms

第4组:

C.ToList 729.65 ms -> [749, 730, 721, 719, 723, 743, 721, 724, 727, 722, 716, 725, 723, 726, 718, 722, 731, 722, 723, 725, 723, 722, 728, 726, 728, 718, 726, 1088, 788, 737, 729, 710, 730, 728, 717, 723, 728, 721, 722, 728, 722, 736, 723, 729, 732, 724, 726, 727, 728, 728, 726, 726, 725, 727, 725, 728, 728, 718, 724, 725, 726, 724, 726, 729, 727, 722, 722, 725, 725, 728, 724, 727, 738, 717, 726, 723, 725, 725, 727, 724, 720, 726, 726, 723, 727, 730, 723, 721, 725, 727, 727, 733, 720, 722, 722, 725, 722, 725, 728, 726]
C.ToArray 788.36 ms -> [748, 740, 742, 797, 1090, 774, 781, 787, 784, 786, 786, 782, 781, 781, 784, 783, 783, 781, 783, 787, 783, 784, 775, 789, 784, 785, 778, 774, 781, 783, 786, 781, 780, 788, 778, 785, 777, 781, 786, 782, 781, 787, 782, 787, 784, 773, 783, 782, 781, 777, 783, 781, 785, 788, 777, 776, 784, 784, 783, 789, 778, 781, 791, 768, 779, 783, 781, 787, 786, 781, 784, 781, 785, 781, 780, 809, 1155, 780, 790, 789, 783, 776, 785, 783, 786, 787, 782, 782, 787, 777, 779, 784, 783, 776, 786, 775, 782, 779, 784, 784]
S.ToList 705.54 ms -> [690, 705, 709, 708, 702, 707, 703, 696, 703, 702, 700, 703, 700, 707, 705, 699, 697, 703, 695, 698, 707, 697, 711, 710, 699, 700, 708, 707, 693, 710, 704, 691, 702, 700, 703, 700, 705, 700, 703, 695, 709, 705, 698, 699, 709, 700, 699, 704, 691, 705, 703, 700, 708, 1048, 710, 706, 706, 692, 702, 705, 695, 701, 710, 697, 698, 706, 705, 707, 707, 695, 698, 704, 698, 699, 705, 698, 703, 702, 701, 697, 702, 702, 704, 703, 699, 707, 703, 705, 701, 717, 698, 695, 713, 696, 708, 705, 697, 699, 700, 698]
S.ToArray 745.01 ms -> [751, 743, 727, 734, 736, 745, 739, 750, 739, 750, 758, 739, 744, 738, 730, 744, 745, 739, 744, 750, 733, 735, 743, 731, 749, 748, 727, 746, 749, 731, 737, 803, 1059, 756, 769, 748, 740, 745, 741, 746, 749, 732, 741, 742, 732, 744, 746, 737, 742, 739, 733, 744, 741, 729, 746, 760, 725, 741, 764, 739, 750, 751, 727, 745, 738, 727, 735, 741, 720, 736, 740, 733, 741, 746, 731, 749, 756, 740, 738, 736, 732, 741, 741, 733, 741, 744, 736, 742, 742, 735, 743, 746, 729, 748, 765, 743, 734, 742, 728, 749]

C1.ToList 32.27 ms -> [36, 31, 31, 32, 31, 32, 31, 30, 32, 30, 30, 30, 34, 32, 31, 31, 31, 31, 31, 31, 31, 32, 38, 51, 68, 57, 35, 30, 31, 31, 30, 30, 33, 30, 31, 34, 31, 34, 32, 31, 31, 31, 31, 32, 30, 30, 31, 30, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 33, 31, 31, 32, 30, 30, 30, 30, 30, 33, 30, 33, 32, 31, 30, 31, 31, 32, 32, 31, 35, 31, 34, 31, 31, 32, 31, 31, 32, 31, 32, 31, 31, 35, 31, 31, 31, 31, 31, 32]
C1.ToArray 56.72 ms -> [58, 56, 57, 57, 59, 58, 58, 57, 56, 59, 57, 55, 55, 54, 56, 55, 56, 56, 57, 59, 56, 55, 58, 56, 55, 55, 55, 55, 58, 58, 55, 57, 57, 56, 57, 57, 57, 57, 59, 59, 56, 57, 56, 57, 57, 56, 57, 59, 58, 56, 57, 57, 57, 58, 56, 56, 59, 56, 59, 57, 57, 57, 57, 59, 57, 56, 57, 56, 58, 56, 57, 56, 57, 59, 55, 58, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 56, 56, 57, 56, 56, 57, 58, 57, 57, 57, 57, 57]
S1.ToList 90.72 ms -> [95, 90, 90, 89, 89, 89, 91, 89, 89, 87, 91, 89, 89, 89, 91, 89, 89, 89, 90, 89, 89, 90, 88, 89, 88, 90, 89, 90, 89, 89, 90, 90, 89, 89, 90, 91, 89, 91, 89, 90, 89, 89, 90, 91, 89, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 89, 90, 89, 91, 89, 90, 89, 90, 89, 90, 89, 96, 89, 90, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 87, 89, 90, 90, 91, 89, 91, 89, 89, 90, 91, 90, 89, 93, 144, 149, 90, 90, 89, 89, 89]
S1.ToArray 131.4 ms -> [130, 128, 127, 134, 129, 129, 130, 136, 131, 130, 132, 132, 133, 131, 132, 131, 133, 132, 130, 131, 132, 131, 130, 133, 133, 130, 130, 131, 131, 131, 132, 134, 131, 131, 132, 131, 132, 131, 134, 131, 131, 130, 131, 131, 130, 132, 129, 131, 131, 131, 132, 131, 133, 134, 131, 131, 132, 132, 131, 133, 131, 131, 130, 133, 131, 130, 134, 132, 131, 132, 132, 131, 131, 134, 131, 131, 132, 132, 131, 130, 138, 130, 130, 131, 132, 132, 130, 134, 131, 131, 132, 131, 130, 132, 133, 131, 131, 131, 130, 131]

C2.ToList 3.21 ms -> [4, 3, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3]
C2.ToArray 3.22 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4]
S2.ToList 41.46 ms -> [42, 40, 41, 40, 42, 40, 40, 40, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 39, 41, 41, 39, 40, 40, 43, 40, 39, 40, 40, 40, 40, 40, 40, 41, 40, 40, 40, 43, 40, 43, 75, 76, 47, 39, 40, 40, 40, 40, 42, 40, 41, 40, 40, 40, 44, 41, 40, 42, 42, 40, 41, 41, 41, 41, 41, 40, 41, 41, 41, 41, 42, 41, 40, 41, 41, 42, 42, 41, 40, 41, 41, 41, 41, 41, 40, 42, 40, 42, 41, 41, 41, 43, 41, 41, 41, 41, 42, 41]
S2.ToArray 41.14 ms -> [42, 41, 41, 40, 40, 40, 40, 41, 41, 42, 41, 42, 41, 41, 41, 42, 41, 41, 42, 41, 41, 41, 41, 41, 42, 40, 41, 40, 42, 40, 42, 41, 40, 42, 41, 41, 43, 42, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 40, 40, 41, 41, 41, 40, 42, 41, 41, 41, 41, 41, 40, 41, 41, 42, 41, 41, 41, 42, 41, 41, 41, 41, 41, 41, 42, 42, 42, 41, 45, 46, 41, 40, 41, 41, 42, 41, 41, 41, 41, 41, 41, 40, 41, 43, 40, 40, 40, 40, 43, 41]

第5组:

C.ToList 757.06 ms -> [770, 752, 752, 751, 778, 763, 761, 763, 747, 758, 748, 747, 754, 749, 752, 753, 756, 762, 750, 753, 756, 749, 755, 757, 755, 756, 755, 744, 753, 758, 747, 751, 759, 751, 761, 755, 746, 752, 752, 749, 746, 752, 753, 755, 752, 755, 754, 754, 966, 937, 749, 759, 748, 747, 754, 749, 755, 750, 746, 754, 757, 752, 753, 745, 758, 755, 761, 753, 751, 755, 755, 752, 746, 756, 755, 746, 742, 751, 751, 749, 752, 751, 756, 756, 755, 742, 749, 754, 749, 756, 753, 751, 754, 752, 751, 754, 753, 749, 755, 756]
C.ToArray 772.8 ms -> [766, 772, 755, 763, 758, 767, 763, 762, 761, 768, 769, 763, 770, 757, 765, 760, 766, 759, 764, 761, 760, 777, 1102, 881, 759, 765, 758, 762, 772, 761, 758, 757, 765, 769, 769, 761, 762, 762, 763, 760, 770, 764, 760, 768, 758, 766, 763, 770, 769, 761, 764, 761, 761, 767, 761, 762, 764, 757, 765, 766, 767, 771, 753, 762, 769, 768, 759, 764, 764, 760, 763, 763, 763, 763, 763, 767, 761, 771, 760, 765, 760, 758, 768, 770, 751, 771, 767, 771, 765, 763, 760, 765, 765, 769, 767, 767, 1193, 774, 767, 764]
S.ToList 704.73 ms -> [682, 708, 705, 699, 705, 704, 695, 703, 702, 699, 701, 708, 699, 702, 703, 701, 701, 699, 701, 707, 707, 700, 701, 705, 700, 697, 706, 702, 701, 706, 699, 692, 702, 697, 707, 704, 697, 698, 699, 699, 702, 703, 698, 697, 702, 703, 702, 704, 694, 697, 707, 695, 711, 710, 700, 693, 703, 699, 699, 706, 698, 701, 703, 704, 698, 706, 700, 704, 701, 699, 702, 705, 694, 698, 709, 736, 1053, 704, 694, 700, 698, 696, 701, 700, 700, 706, 706, 692, 698, 707, 703, 695, 703, 699, 694, 708, 695, 694, 706, 695]
S.ToArray 744.17 ms -> [746, 740, 725, 740, 739, 731, 746, 760, 735, 738, 740, 734, 744, 748, 737, 744, 745, 727, 736, 738, 728, 743, 745, 735, 748, 760, 739, 748, 762, 742, 741, 747, 733, 746, 758, 742, 742, 741, 724, 744, 747, 727, 740, 740, 729, 742, 757, 741, 740, 742, 726, 739, 746, 1133, 749, 737, 730, 740, 747, 733, 747, 752, 731, 747, 742, 730, 741, 749, 731, 749, 743, 730, 747, 742, 731, 737, 745, 734, 739, 735, 727, 743, 752, 731, 744, 742, 729, 740, 746, 731, 739, 746, 733, 745, 743, 733, 739, 742, 727, 737]

C1.ToList 31.71 ms -> [35, 32, 32, 30, 31, 33, 31, 32, 32, 31, 31, 32, 32, 33, 32, 31, 31, 32, 31, 32, 32, 32, 31, 32, 33, 32, 31, 31, 31, 32, 31, 34, 31, 31, 32, 33, 32, 32, 31, 32, 34, 32, 31, 32, 33, 31, 32, 32, 31, 32, 32, 32, 32, 32, 32, 31, 31, 32, 31, 33, 30, 31, 32, 30, 30, 33, 32, 32, 34, 31, 31, 31, 31, 32, 31, 31, 31, 31, 32, 31, 31, 33, 31, 32, 32, 32, 33, 32, 31, 31, 31, 31, 31, 32, 32, 33, 32, 31, 31, 32]
C1.ToArray 59.53 ms -> [63, 57, 58, 58, 57, 59, 59, 57, 60, 131, 127, 67, 58, 56, 59, 56, 57, 58, 58, 58, 57, 59, 60, 57, 57, 59, 60, 57, 57, 57, 58, 58, 58, 58, 57, 57, 61, 57, 58, 57, 57, 57, 57, 57, 58, 58, 58, 58, 57, 58, 59, 57, 58, 57, 57, 59, 58, 58, 59, 57, 59, 57, 56, 56, 59, 56, 56, 59, 57, 58, 58, 58, 57, 58, 59, 59, 58, 57, 58, 62, 65, 57, 57, 57, 58, 60, 59, 58, 59, 57, 58, 57, 58, 59, 58, 58, 58, 59, 60, 58]
S1.ToList 82.78 ms -> [87, 82, 83, 83, 82, 82, 83, 84, 82, 83, 84, 84, 84, 82, 82, 84, 82, 84, 83, 84, 82, 82, 82, 81, 83, 83, 83, 84, 84, 82, 82, 83, 83, 83, 82, 83, 85, 83, 82, 82, 84, 82, 82, 83, 83, 83, 82, 82, 82, 83, 82, 83, 82, 84, 82, 83, 82, 83, 82, 82, 82, 84, 82, 83, 82, 82, 86, 83, 83, 82, 83, 83, 83, 82, 84, 82, 83, 81, 82, 82, 82, 82, 83, 83, 83, 82, 83, 84, 83, 82, 83, 83, 83, 82, 83, 84, 82, 82, 83, 83]
S1.ToArray 122.3 ms -> [122, 119, 119, 120, 119, 120, 120, 121, 119, 119, 122, 120, 120, 120, 122, 120, 123, 120, 120, 120, 121, 123, 120, 120, 120, 121, 120, 121, 122, 120, 123, 119, 121, 118, 121, 120, 120, 120, 119, 124, 119, 121, 119, 120, 120, 120, 120, 120, 122, 121, 123, 230, 203, 123, 119, 119, 122, 119, 120, 120, 120, 122, 120, 121, 120, 121, 120, 121, 120, 121, 120, 120, 120, 121, 122, 121, 123, 119, 119, 119, 119, 121, 120, 120, 120, 122, 121, 122, 119, 120, 120, 121, 121, 120, 121, 120, 121, 118, 118, 118]

C2.ToList 3.43 ms -> [5, 3, 4, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 4, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 6, 4, 4, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 4, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3]
C2.ToArray 3.48 ms -> [3, 3, 3, 3, 4, 4, 3, 4, 4, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 3, 3, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 4, 3]
S2.ToList 41.47 ms -> [41, 41, 49, 67, 82, 41, 41, 40, 40, 40, 40, 40, 41, 40, 40, 40, 40, 40, 41, 40, 42, 42, 40, 40, 41, 41, 41, 40, 41, 40, 41, 40, 41, 40, 42, 41, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 41, 41, 41, 42, 40, 41, 40, 40, 40, 42, 40, 41, 42, 41, 42, 41, 42, 40, 41, 41, 41, 41, 41, 41, 41, 41, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 40, 41, 41, 41, 41, 41, 43, 40, 40, 41, 42, 41]
S2.ToArray 40.62 ms -> [42, 41, 44, 40, 40, 40, 40, 41, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 41, 40, 41, 40, 40, 41, 42, 41, 41, 41, 40, 40, 40, 40, 40, 41, 41, 42, 40, 41, 41, 41, 41, 41, 40, 42, 40, 40, 41, 41, 41, 40, 41, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 40, 41, 42, 40, 41, 41, 42, 41, 41, 40, 41, 40, 41, 40, 41, 41, 40, 40, 40, 41, 41, 40, 40, 40, 40, 40]

由于stackoverflow限制了答案的字符数,因此省略了Group2和Group3的示例列表。

如您所见,使用ToListToArry在大多数情况下。

在处理运行时计算的IEnumerable<T>目的,如果通过计算所带来的负荷比内存分配重和复制的操作ToListToArray,视差是微不足道的(C.ToList vs C.ToArrayS.ToList vs S.ToArray)。

仅在非运行时计算的IEnumerable<T>对象(C1.ToList vs C1.ToArrayS1.ToList vs S1.ToArray)上可以观察到差异。但是对于一百万个小物体,绝对差(<60ms)仍然可以接受IEnumerable<T>。实际上,差异是由的实现决定Enumerator<T>IEnumerable<T>。因此,如果您的程序对此确实非常敏感,则必须进行概要分析,概要分析,概要分析!最后,您可能会发现瓶颈不在ToListToArray,而是枚举器的详细信息。

而且,结果C2.ToList vs C2.ToArrayS2.ToList vs S2.ToArray那个,你真的不需要关心节目ToListToArray在非运行时计算ICollection<T>对象。

当然,这只是我的机器上的结果,这些操作在不同机器上花费的实际时间将不一样,您可以使用上面的代码在您的机器上查找。

您需要做出选择的唯一原因是,您对List<T>或具有特定需求T[],如@Jeppe Stig Nielsen的回答所述。


1

对于有兴趣在另一个Linq-to-sql中使用此结果的人,例如

from q in context.MyTable
where myListOrArray.Contains(q.someID)
select q;

那么无论您为myListOrArray使用列表还是数组,生成的SQL都是相同的。现在,我知道有人可能会问为什么在此语句之前甚至要枚举,但是从IQueryable生成的SQL与(列表或数组)生成的SQL之间是有区别的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.