[.net] LINQ 쿼리에서 ToList () 또는 ToArray ()를 호출하는 것이 더 낫습니까?

나는 종종 내가 그것을 선언하는 곳에서 쿼리를 평가하고 싶은 경우에 빠진다. 내가 여러 번 반복해야하기 때문에 이것은 보통 는 계산할 비싸다. 예를 들면 다음과 같습니다.

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()메모리가 두 번 할당되지 않는다는 지식을 안전하고 안전하게 전화해야합니까 ?



답변

다른 제약 조건을 충족시키기 위해 단순히 배열이 필요하지 않으면 사용해야합니다 ToList. 대부분의 시나리오에서 ToArray보다 더 많은 메모리를 할당합니다ToList 합니다.

둘 다 스토리지에 스토리지를 사용하지만 ToList보다 유연한 제약이 있습니다. 최소한 컬렉션의 요소 수만큼 배열이 커야합니다. 배열이 더 크면 문제가되지 않습니다. 하나ToArray 배열의 크기는 요소 수와 정확히 일치해야합니다.

이 제약 조건을 충족시키기 위해 ToArray종종보다 하나 이상의 할당을 수행 ToList합니다. 배열이 충분히 커지면 정확히 정확한 크기의 배열을 할당하고 요소를 해당 배열로 다시 복사합니다. 이것을 피할 수있는 유일한 시간은 배열에 대한 증가 알고리즘이 저장해야 할 요소의 수와 정확히 일치하는 경우입니다 (소수로).

편집하다

두 사람이 여분의 사용하지 않은 메모리를 갖는 결과에 대해 물었습니다. List<T> 값 .

이것은 유효한 관심사입니다. 생성 된 컬렉션이 오래 지속되고 생성 된 후에는 수정되지 않으며 Gen2 힙에 착륙 할 가능성이 높은 경우 추가 할당을 수행하는 것이 좋습니다.ToArray .

일반적으로 나는 이것이 드문 경우라고 생각합니다. 많은 것을 보는 것이 훨씬 일반적입니다.ToArray다른 짧은 수명의 메모리 사용으로 즉시 전달되는 호출ToList 입니다.

여기서 핵심은 프로파일 링, 프로파일 링 및 기타 프로파일 링입니다.


답변

List<T>동적 크기의 배열로 구현 되기 때문에 성능 차이는 중요하지 않습니다 . 중 하나를 호출 ToArray()(내부 사용하는 Buffer<T>배열을 성장 클래스) 또는 ToList()를 호출하는 (List<T>(IEnumerable<T>) 생성자)를 배열에 넣어 그것은 그들 모두를 맞을 때까지 배열을 성장의 문제가 끝나게됩니다.

이 사실을 구체적으로 확인하려면 Reflector에서 해당 메소드의 구현을 확인하십시오. 거의 동일한 코드로 요약됩니다.


답변

(7 년 후 …)

몇 가지 다른 (좋은) 답변은 발생할 미세한 성능 차이에 집중했습니다.

이 게시물은 배열 ( )에 의해 생성 된 것 사이에 존재 하는 의미 론적 차이 를 언급 한 보충 자료입니다 .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
삼
4
5
6
7
8
900
10

이는 열거자가 IEnumarator<int>반환 한 int[]후 배열이 수정되었는지 여부를 추적하지 않음을 나타냅니다 .

로컬 변수를 source로 선언 했습니다 IList<int>. 그런 식으로 C # 컴파일러가 foreach명령문을 for (var idx = 0; idx < source.Length; idx++) { /* ... */ }루프 와 동등한 것으로 최적화하지 않도록 합니다. 이것은 var source = ...;대신 사용하면 C # 컴파일러가 할 수있는 일 입니다. 현재 사용중인 .NET 프레임 워크 버전에서 여기에 사용 된 실제 열거자는 비공개 참조 유형 System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]이지만 물론 구현 세부 사항입니다.

이제 .ToArray()로 변경 하면 다음 .ToList()과 같은 결과 만 얻을 수 있습니다.

1
2
삼
4
5

a로 다음 System.InvalidOperationException말 블로우 업 :

수집이 수정되었습니다. 열거 작업이 실행되지 않을 수 있습니다.

이 경우 기본 열거자는 public mutable value-type입니다 System.Collections.Generic.List`1+Enumerator[System.Int32]( IEnumerator<int>이 경우 상자 안에 상자가 있으므로 사용 IList<int>).

결론적으로, keep에 의해 생성 된 열거자는 열거List<T>동안리스트가 변경되는지 여부를 추적하지만,에 의해 생성 된 열거자는T[]그렇지 않습니다. .ToList()와사이에서 선택할 때이 차이점을 고려하십시오.ToArray().

사람들은 종종 열거 자의 수명 동안 수정되었는지 여부를 추적하는 컬렉션을 추가 .ToArray() 하거나 .ToList()회피하기 위해 하나를 추가 합니다.

(사람이 알고 싶은 경우 방법 (가) List<>수집이 수정되었는지 여부에 대한 추적, 개인 필드가 _version매번 변경되는이 클래스에 List<>업데이트됩니다.)


답변

성능 차이가 중요하지 않다는 @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 ()를 사용할 수도 List<T>있습니다.


답변

ToList()IEnumerable<T>(예를 들어 ORM에서) 사용하는 경우 일반적으로 선호됩니다 . 처음에 시퀀스 길이를 모르는 경우 ToArray()List와 같은 동적 길이 컬렉션을 만든 다음 배열로 변환하면 시간이 더 걸립니다.


답변

메모리는 항상 두 번 또는 그에 가까운 것으로 할당됩니다. 배열의 크기를 조정할 수 없으므로 두 방법 모두 일종의 메커니즘을 사용하여 증가하는 컬렉션에서 데이터를 수집합니다. 글쎄, 목록 자체가 점점 커지고 있습니다.

이 목록은 어레이를 내부 스토리지로 사용하며 필요한 경우 용량을 두 배로 늘립니다. 즉, 항목의 평균 2/3가 최소 한 번 재 할당되고, 적어도 두 번 재 할당 된 것의 절반, 적어도 세 번 이상은 재 할당 된 것입니다. 즉, 각 항목은 평균 1.3 배로 재 할당되었으므로 오버 헤드가 그리 크지 않습니다.

또한 문자열을 수집하는 경우 컬렉션 자체에는 문자열에 대한 참조 만 포함되며 문자열 자체는 재 할당되지 않습니다.


답변

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)