linq가 표면에 나타나는 것보다 더 효율적입니까?


13

이런 식으로 쓰면 :

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)

이것은 다음과 같습니다.

var results1 = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue)
        results1.Add(t);

var results2 = new List<Thing>();
foreach(var t in results1)
    if(t.IsSomeOtherValue)
        results2.Add(t);

또는 다음과 같이 작동하는 표지 아래에 마술이 있습니까?

var results = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue && t.IsSomeOtherValue)
        results.Add(t);

아니면 완전히 다른 것입니까?


4
ILSpy에서 볼 수 있습니다.
ChaosPandion

1
ILSpy가 당신의 친구라는 첫 번째이지만 두 번째 ChaosPandion의 대답보다 두 번째 예와 비슷합니다.
Michael

답변:


27

LINQ 쿼리는 지연 됩니다. 그것은 코드를 의미합니다 :

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

거의하지 않습니다. 원래 열거 형 ( mythings)은 결과 열거 형 ( things)이 예를 들어 foreach루프 .ToList(), 또는로 소비 될 때만 열거됩니다 .ToArray().

를 호출하면 things.ToList()후자의 코드와 대략 동일하며 열거 자에서 약간의 (일반적으로 중요하지 않은) 오버 헤드가 있습니다.

마찬가지로 foreach 루프를 사용하는 경우 :

foreach (var t in things)
    DoSomething(t);

성능면에서 다음과 유사합니다.

foreach (var t in mythings)
    if (t.IsSomeValue && t.IsSomeOtherValue)
        DoSomething(t);

열거 형에 대한 게으름 접근 방식의 성능 이점 중 일부는 (모든 결과를 계산하고 목록에 저장하는 것과는 대조적으로) 메모리를 거의 사용하지 않으며 (한 번에 하나의 결과 만 저장되므로) 중요한 것은 없습니다 초기 비용.

열거 형이 부분적으로 만 열거되는 경우 특히 중요합니다. 이 코드를 고려하십시오.

things.First();

LINQ가 구현되는 방식은 mythings위치 조건과 일치하는 첫 번째 요소까지만 열거됩니다. 해당 요소가 목록에서 일찍 나오는 경우 성능이 크게 향상 될 수 있습니다 (예 : O (n) 대신 O (1)).


1
LINQ와 사용하는 해당 코드의 성능 차이 중 하나 foreach는 LINQ가 오버 헤드가있는 대리자 호출을 사용한다는 것입니다. 이는 조건이 매우 빠르게 실행될 때 중요 할 수 있습니다 (자주 수행하는 작업).
svick

2
이것이 열거 자 오버 헤드의 의미입니다. 일부 (드문) 경우에는 문제가 될 수 있지만, 내 경험상 자주 발생하지 않는 경우가 많습니다. 일반적으로 소요되는 시간이 시작에 걸리는 시간이 너무 짧거나 다른 작업에 비해 훨씬 중요합니다.
Cyanfish

Linq의 게으른 평가에 대한 과도한 제한은 ToListor 와 같은 방법을 제외하고 열거의 "스냅 샷"을 취할 수있는 방법이 없다는 것 ToArray입니다. 그러한 것이 제대로 구축 되었다면 IEnumerable, 모든 것을 생성하지 않고도 미래에 변할 수있는 모든 측면을 "스냅 샷"하도록 목록에 요청할 수 있었을 것입니다.
supercat

7

다음 코드 :

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

게으른 평가로 인해 아무것도 발생하지 않습니다.

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)
    .ToList();

평가가 시작되기 때문에 다릅니다.

의 각 항목은 mythings첫 번째에 제공 Where됩니다. 통과하면 두 번째에 제공 Where됩니다. 통과하면 출력의 일부가됩니다.

그래서 이것은 다음과 같이 보입니다 :

var results = new List<Thing>();
foreach(var t in mythings)
{
    if(t.IsSomeValue)
    {
        if(t.IsSomeOtherValue)
        {
            results.Add(t);
        }
    }
}

7

지연 된 실행은 제쳐두고 (다른 답변은 이미 설명했지만 다른 세부 사항을 지적하겠습니다) 두 번째 예와 비슷합니다.

그냥 당신이 전화를 가정 해 봅시다 ToListthings.

의 구현 Enumerable.Where은를 반환합니다 Enumerable.WhereListIterator. 당신이 호출하면 Where그에 WhereListIterator(일명 체인 Where, 당신은 더 이상 전화 -calls) Enumerable.Where하지만, Enumerable.WhereListIterator.Where실제로 조건을 결합, (사용 Enumerable.CombinePredicates).

그래서 더 비슷 if(t.IsSomeValue && t.IsSomeOtherValue)합니다.


"Enumerable.WhereListIterator를 반환합니다"라는 메시지가 표시됩니다. 아마도 매우 간단한 개념이지만, 이것이 ILSpy에서 간과했던 것입니다. 감사합니다
ConditionRacer

보다 깊이있는 분석에 관심이있는 경우 Jon Skeet의이 최적화 재 구현을 참조하십시오 .
Servy

1

아니요 동일하지 않습니다. 당신의 예에서 thingsIEnumerable이 시점에서 여전히 반복자가 아닌 실제 배열이나리스트이다. 또한 things사용되지 않으므로 루프는 평가되지 않습니다. 이 유형을 IEnumerable사용하면 yieldLinq 명령어에 의해 -ed 요소를 반복 하고 더 많은 명령어로 더 처리 할 수 ​​있습니다. 결국에는 실제로 하나의 루프 만 있습니다.

그러나 즉시로 같은 명령을 추가 .ToArray()하거나 .ToList()당신이 따라서, 실제 데이터 구조의 생성을 주문하여 체인에 경계를두고있어.

이 관련 SO 질문을 참조하십시오 : https : //.com/questions/2789389/how-do-i-implement-ienumerable

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.