2013-08-09 2 views
3

항복 수익률에 관한 몇 가지 테스트를하고 있는데 정상적인 수익보다 느린 것으로 나타났습니다.'yield return'은 'old school'이 반환하는 것보다 느 립니 까?

필자는 값 변수 (int, double 등)와 일부 참조 유형 (문자열 등)을 테스트했습니다 ... 그리고 두 경우 모두 yield return이 더 느립니다. 왜 그것을 사용 하는가? 내 예를 밖으로

확인 : 하나

public class YieldReturnTeste 
{ 
    private static IEnumerable<string> YieldReturnTest(int limite) 
    { 
     for (int i = 0; i < limite; i++) 
     { 
      yield return i.ToString(); 
     } 
    } 

    private static IEnumerable<string> NormalReturnTest(int limite) 
    { 
     List<string> listaInteiros = new List<string>(); 

     for (int i = 0; i < limite; i++) 
     { 
      listaInteiros.Add(i.ToString()); 
     } 
     return listaInteiros; 
    } 

    public static void executaTeste() 
    { 
     Stopwatch stopWatch = new Stopwatch(); 

     stopWatch.Start(); 

     List<string> minhaListaYield = YieldReturnTest(2000000).ToList(); 

     stopWatch.Stop(); 

     TimeSpan ts = stopWatch.Elapsed; 


     string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", 

     ts.Hours, ts.Minutes, ts.Seconds, 

     ts.Milliseconds/10); 

     Console.WriteLine("Yield return: {0}", elapsedTime); 

     //**** 

     stopWatch = new Stopwatch(); 

     stopWatch.Start(); 

     List<string> minhaListaNormal = NormalReturnTest(2000000).ToList(); 

     stopWatch.Stop(); 

     ts = stopWatch.Elapsed; 


     elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", 

     ts.Hours, ts.Minutes, ts.Seconds, 

     ts.Milliseconds/10); 

     Console.WriteLine("Normal return: {0}", elapsedTime); 
    } 
} 
+2

시계 메모리 사용량! List -method는 O (n) 메모리를 사용하는 반면 yield/Enumerator-method는 O (1) 메모리를 소비합니다. 매우 큰 목록의 경우 이는 매우 중요합니다. 또한 임시 목록을 추가하지 않고도 쉽게 열거 형을 연결할 수 있습니다. 이것은 좀 더 일반적인 토론입니다 : http://stackoverflow.com/questions/3628425/ienumerable-vs-list-what-to-use-how-do-they-work –

+0

두 가지 이슈 : 먼저 'NormalReturnTest'가 사전 처리되어야합니다. 그것의리스트 길이를'limite'로 초기화하십시오. 둘째,'.ToList()'메소드가'List'에서 작동 할 때 특별한 검사를 받는다는 사실은 실제로 그 기본 배열을 치고 목록을 반복하고 항목 하나를 복사하는 대신 배열 복사를 수행한다는 것을 확신합니다 하나는 완전히 다른 결과를 낳습니다. 'yield return' 열거 형의'.ToList()'는 모든 요소를 ​​반복하고 배열을 작성해야합니다 (2000000 개 요소를 치기 위해 여러 크기 조정이 필요함). 당신은 잘못된 것을 측정하고 있습니다. –

+0

또 다른 문제는 일반적인 벤치마킹 문제 일뿐입니다. 예를 들어, 단일 실행이 아닌'ToList'의 _many_ 실행을 수행해야합니다. 둘째 시간 차이는 (내 컴퓨터에서) 670ms와 690ms (그리고 크게 변동)입니다. 시간을 변경할 수있는 다른 무관 한 처리 문제가 있기 때문에 너무 많이 읽지는 ​​않습니다. –

답변

11

http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspxFile.ReadAllLinesFile.ReadLines의 차이를 고려하십시오.

ReadAllLines은 모든 행을 메모리에로드하고 string[]을 반환합니다. 파일이 작 으면 좋고 좋아요. 파일이 메모리 용량보다 크면 메모리가 부족합니다.

ReadLines 반면에 yield return을 사용하면 한 번에 한 줄씩 반환 할 수 있습니다. 그것으로 모든 크기의 파일을 읽을 수 있습니다. 전체 파일을 메모리에로드하지 않습니다.

단어 "foo"가 포함 된 첫 번째 줄을 찾고 종료하고 싶다고합시다. ReadAllLines을 사용하면 "foo"가 첫 번째 줄에서 발생하더라도 전체 파일을 메모리로 읽어야합니다. ReadLines으로 한 줄만 읽습니다. 어느 것이 더 빠를 것입니까?

그게 유일한 이유는 아닙니다. 파일을 읽고 각 행을 처리하는 프로그램을 생각해보십시오.

string[] lines = File.ReadAllLines(filename); 
for (int i = 0; i < lines.Length; ++i) 
{ 
    // process line 
} 

는이 파일을 읽을 걸리는 시간, 플러스 라인을 처리하는 데 시간과 동일하다 실행하는 프로그램을 걸리는 시간 : File.ReadAllLines를 사용하면 끝낼. 처리 시간이 오래 걸리므로 여러 스레드로 속도를 높이고 싶다고 상상해보십시오. 그래서 당신은 다음과 같이합니다 :

lines = File.ReadAllLines(filename); 
Parallel.Foreach(...); 

독서는 단일 스레드입니다. 주 스레드가 전체 파일을로드 할 때까지 여러 스레드를 시작할 수 없습니다. 다른 라인 읽어되고 있음을 동시에 처리하는 즉시 다중 스레드를 시작

Parallel.Foreach(File.ReadLines(filename), line => { ProcessLine(line); }); 

:

ReadLines으로하지만, 당신이 뭔가를 할 수 있습니다. 따라서 읽기 시간은 처리 시간과 겹치므로 프로그램이 더 빠르게 실행됩니다.

필자는 파일을 사용하여 예제를 보여 주지만 개념을 쉽게 보여주기 때문에 보여 주지만 인 메모리 컬렉션에서도 마찬가지입니다.yield return을 사용하면 메모리 사용량이 적어지고 특히 컬렉션의 일부만보아야하는 메서드 (Enumerable.Any, Enumerable.First 등)를 호출 할 때 잠재적으로 더 빠릅니다.

+1

"ReadLines"메서드가 모든 파일을 메모리에 저장하지 않으면 읽는 모든 행에 디스크 액세스가 수행됩니까? –

+1

@MichelAlmeida : 아니요. 기본 스트림은 파일을 4 킬로바이트 이상의 블록으로로드 한 다음 그 파일에서 행을 구문 분석합니다. 따라서 디스크 읽기 횟수가 최소화됩니다. 또한 운영 체제는 일반적으로 일부 유형의 미리 읽기 캐싱을 사용할 수 있으므로 스트림이 더 많은 정보를 요청하면 이미 메모리에 있고 "읽기"는 단지 한 메모리 위치에서 다른 위치로 데이터를 복사하는 것입니다. –

+0

hm. 그렇다면 여전히 하나 이상의 디스크 액세스를 수행하지만 "캐싱"기능을 사용하면 모든 파일을 읽고 메모리에 모두 저장하는 것보다 낫습니다. 참고 : 젠장 할 수 없다. OO –

2

, 그것은 편리한 기능입니다. 둘째, 게으른 반환을 할 수 있습니다. 즉, 값을 가져올 때만 평가됩니다. 이는 DB 쿼리와 같은 요소 나 완전히 반복하지 않으려는 컬렉션에서 매우 중요합니다. 셋째, 일부 시나리오에서는 더 빠를 수 있습니다. 네, 그 차이점은 무엇입니까? 아마 작은, 그래서 마이크로 최적화.

1

C# 컴파일러는 반복기 블록 (yield return)을 상태 시스템으로 변환하기 때문에. 상태 머신은이 경우 매우 비쌉니다.

여기에서 자세한 내용을보실 수 있습니다 :

+0

감사합니다. 도움이되었습니다 –

0

알고리즘 결과를 얻으려면 yield return을 사용했습니다. 모든 결과는 이전 결과를 기반으로하지만 모든 결과가 모두 필요하지는 않습니다. 나는 각 결과를 검사하고 결과를 얻을 경우 foreach 루프를 깨기 위해 yield return을 사용하여 foreach를 사용했습니다.

알고리즘은 복잡하지 않았으므로 각 수익률 반환 사이에 상태를 저장하는 것과 관련된 괜찮은 작업이 있다고 생각합니다.

전통적인 수익률보다 3 % -5 % 빠르다는 것을 알았지 만, 모든 결과를 생성 할 필요가없는 형태의 개선은 성능 손실보다 훨씬 큽니다.

0

.ToList() IEnumerable의 지연된 반복을 실제로 완료하는 데 필요한 반면 핵심 부분을 측정하는 것을 방해합니다.

적어도 그것은 공지 크기리스트를 초기화하는 것이 중요하다 :

CONST의 INT listSize = 2000000; var tempList = 새 목록 (listSize);

...

목록 tempList = YieldReturnTest (listSize) .ToList();

비고 : 두 번 호출 모두 내 컴퓨터에서 거의 같은 시간이 걸렸습니다. 아무런 차이가 없었습니다 (replo에서는 모노 4).

관련 문제