Microsoft MVP성태의 닷넷 이야기
string.Join()과 Enumerable.Aggregate()의 차이가 궁금합니다. [링크 복사], [링크+제목 복사],
조회: 16402
글쓴 사람
이성환 (vactorman at naver.com)
홈페이지
첨부 파일



List 요소의 특정 멤버들을 한 문자열로 나열하기 위해

string.Join()과 Aggregate() 를 이용해 구분자를 삽입하여 문자열로 연결하는 테스트를 하다가

두 메서드의 수행 속도가 예상한 것과 다르다는 것을 깨달았습니다.


그리고 또 LINQPad와 실제 vs2012에서 작성한 Console 프로그램의 성능 차이가 확인돼서 이렇게 질문 올립니다.


말로 설명하면 더 길어질거 같아 테스트한 코드를 먼저 보면

public class Person
{
    public string Name { get; set; }
}

이런 클래스가 있고




var list = new List<Person>();

for (var i = 0; i < 1000; i++)
{
    var newName = new Person { Name = i.ToString() };
    list.Add(newName);
}

var start1 = Stopwatch.GetTimestamp();

var result1 = list.Select(person => person.Name).Aggregate((current, next) => current + "," + next);

Console.WriteLine(result1);

var elapsed1 = (Stopwatch.GetTimestamp() - start1) / (double)Stopwatch.Frequency;
Console.WriteLine();
Console.WriteLine("Aggregate : " + elapsed1);

var start2 = Stopwatch.GetTimestamp();

var result2 = string.Join(",", list.Select(person => person.Name));
Console.WriteLine(result2);

var elapsed2 = (Stopwatch.GetTimestamp() - start2) / (double)Stopwatch.Frequency;

Console.WriteLine();

Console.WriteLine("Join : " + elapsed2);


이렇게 리스트의 요소 중 Name 멤버들을 구분자로 넣어서 하나의 문자열을 만드는 과정을 구성했습니다.

그리고 각 문자열 생성 작업 시간을 측정해 보았는데

string.Join() 과 Aggregate() 의 수행 시간이 들쑥날쑥 합니다.

ReBuild 할 때마다 어떨 때는 Join()이 빨랐다가 또 어떨때는 Aggregate()가 빨랐다가

근소한 차이로 두 메서드의 결과가 엎치락 뒤치락 하는 등

일정한 결과가 나오지 않았는데요.
(List 개수 1000개 정도에서는 그랬습니다.)

생성된 문자열 객체를 사용하지 않은 상태(Console.WriteLine으로 문자열을 찍지 않은 상태)에서 시간측정을 하면

Join()이 10~20배 정도로 빠릅니다.

그리고 List의 크기가 커지면 (10000개 이상으로 늘리면) 그 때부터는 확실히 Join()이 빨라집니다.


궁금한 점은

1. Console.WriteLine()으로 생성된 문자열을 찍지 않은 상태라면 Aggregate()가 Join()보다 더 빨라야 하는 게 아닌가요?

Console.WriteLine()으로 생성된 문자열을 찍지 않은 상태라면

Aggregate()의 경우 실제 요소에 접근하지 않았으므로 메서드 완료 속도가 더 빨라야 하는게 아닌가

하는 것이 처음 생각이었는데 결과가 정반대여서 의문이 생겼습니다.

어차피 list.Select(person => person.Name) 이 구문까지는 Join()과 Aggregate()가 모두 같은 조건이라

Select() 호출까지의 결과는 크게 다르지 않았을 것인데

정작 Aggregate()의 시간이 더 많이 걸린다는 것은 실제 요소 접근 구문이 없어도 누산 작업을 수행했고

누산 시 모든 요소에 접근하는 게 아닌가 하는 생각이 듭니다.


그렇다쳐도 구현내용을 겉핥기로만 봤을 때

Join()은 StringBuilder를 이용해 while을 돌면서 Append 한 것을 리턴하지만

Aggregate()는 while을 돌면서 그냥 func 를 수행한 결과를 이전 요소에 할당하는 정도로 끝나기 때문에 StringBuilder 보다 더 빨라야 한다고 생각했는데
(타입이 이미 string이므로 메모리 상의 손해는 있을 지언정 속도는 더 빨라야 한다고 생각했습니다.)

왜 이런 결과가 나왔는지 궁금합니다.


그런데 사실 이 테스트는 vs2012보다 LINQPad에서 먼저 수행했습니다.

첨부한 파일처럼 수행했었는데요.

LINQPad에서는 일정하게 Join()이 Aggregate보다 훨씬 빠릅니다. 그리고 리스트의 크기를 크게 할수록 속도차이가 확연해집니다.
(이 부분은 Console과 같습니다.)

헌데 가장 이해가 안 되는 부분은 생성된 문자열을 Dump()를 이용해 모두 결과창에 표시했을 때 입니다.

vs2012의 Console 테스트에서는 생성된 문자열을 Console에 찍었을 경우

Release나 Debug 모두 속도차이가 그리 크지 않게 나왔습니다.
(물론 이 경우에도 리스트의 크기가 클 수록 Join()의 속도가 더 빠릅니다만 LINQPad만큼의 차이는 나지 않았습니다.)


그런데 LINQPad에서는 결과를 모두 표시해도 Join()이 훨씬 빠르게 수행되는 것을 확인했습니다.

혹시나 해서 JITter의 영향이 있을까봐 테스트 구문 수행 전에

var tempList =Enumerable.Range(0, 100);
tempList.Aggregate((current, next) => current + next);
string.Join(",", tempList);

list.Select(person => person.Name);

뭐 이런 코드를 먼저 호출하고 시작하는 뻘짓도 해봤지만

별 차이가 없었습니다.



이렇게 차이가 나는 것도 의문이고

그렇다면 실행 결과는 LINQPad 보다 Console 프로그램의 결과를 더 신뢰해야하는 건지 의문이 듭니다.



보통 프로토타이핑이나 테스트 코드를 만드는 작업은 LINQPad를 주로 사용하는데

이렇게 테스트를 하고 난 후부터는 구문상의 오류 따위가 아니라 이런 퍼포먼스에 관련된 작업이라면

반드시 Visual Studio로 테스트를 해봐야겠다는 생각이 듭니다.

아니면 뭔가 제가 테스트를 잘못한 걸까요?


도움 부탁드립니다.










[최초 등록일: ]
[최종 수정일: 2/28/2014]


비밀번호

댓글 작성자
 



2014-03-01 03시10분
LINQ의 표준 연산자가 모두 지연 처리를 하는 것은 아닙니다. IEnumerable 계열을 반환하지 않는 단일값 반환 연산자들은 그 즉시 처리되는 것이 일반적입니다. Aggregate의 경우 반환값이 TSource 단일값인데 이는 LINQ식이 평가되면서 곧바로 실행됩니다.

성능 차이에 대해서는 제가 일단 관심이 없어서 넘어가겠습니다. 어차피 .NET Reflector로 조사하면 뭔가 환경적인 요인이나 테스트 코드 상의 차이가 있을 텐데... 이 부분은 이성환님이 좀더 ^^ 살펴보시고 한편의 멋진 글을 써주시길 기대하겠습니다. (나중에 이 질문에 흥미가 가면 혹시 쓸지도 모르겠습니다. ^^)
정성태
2014-03-01 05시44분
[이성환] 답변 감사드립니다.

Aggregate()가 지연처리 하지 않는 것은 잘 몰랐던 부분입니다. 결과가 지연처리가 아닌 것처럼 나와서 그렇게만 추측한 정도였죠
사실 LINQ 표준 연산자가 단일값을 반환할 때 지연처리 한다는 사실을 몰랐습니다. 하나 또 배웠습니다.

성능 상의 차이 역시 사실
제가 그 정도로 깊게 조사해 볼 수 있는 능력이 없어서 리플렉터로 구현된 코드를 보고 추측하는 정도라
더 진전이 없어 질문을 드린 건데요. ;ㅅ; 공부가 더 필요한 걸 절실히 느낍니다.

여튼 답변 주셔서 감사합니다. .(__).

[guest]

... 91  92  93  [94]  95  96  97 
NoWriterDateCnt.TitleFile(s)
111정성태3/19/20059770    답변글 [답변]: 닷넷 프레임워크 배포
107안연준3/17/20059012[-_-]스마트 클라이언트에 관련 된 질문[-_-]
110정성태3/19/20059829    답변글 [답변]: [-_-]스마트 클라이언트에 관련 된 질문[-_-]
116안연준3/21/20059401        답변글 [답변]: 친절한 답변 고맙습니다.
98김용국3/18/200511172정성태님... 연결이 준비됬습니다
99정성태3/2/200512063    답변글 [답변]: 정성태님... 연결이 준비됬습니다
100김용국3/18/20058905        답변글 [답변]: 죄송합니다! 급히올리느라 file path의 변경을 하지않고 그냥올렸네요....!
101정성태3/2/20059157            답변글 [답변]: [답변]: 죄송합니다! 급히올리느라 file path의 변경을 하지않고 그냥올렸네요....!
102김용국3/18/20059208                답변글 [답변]: 거듭죄송하네요..... 후~~~주소를 변경하고 빌드를 다시 했습니다....
103정성태3/2/20059102                    답변글 [답변]: [답변]: 거듭죄송하네요..... 후~~~주소를 변경하고 빌드를 다시 했습니다....
104김용국3/2/20059319                        답변글 [답변]: 네... 빨리 검토해 봐 주셔서 감사합니다~~
105정성태3/2/20058546                            답변글 [답변]: [답변]: 네... 빨리 검토해 봐 주셔서 감사합니다~~
106김용국3/2/20059019                                답변글 [답변]: 답변감사합니다!
91김용국2/28/200510833IE에 WindowsFormControl을 올려 실행하면 이런에러가 나네요???파일 다운로드1
92정성태2/28/200512137    답변글 [답변]: IE에 WindowsFormControl을 올려 실행하면 이런에러가 나네요???
93김용국2/28/200511076        답변글 [답변]: 답변감사합니다... 재질문을 드립니다
94정성태2/28/200511659            답변글 [답변]: [답변]: 답변감사합니다... 재질문을 드립니다
95김용국2/28/200511734                답변글 [답변]: IE주소창에서 해당주소로 실행을 해보니....디버깅 PopUp화면이...^
96정성태2/28/200510789                    답변글 [답변]: [답변]: IE주소창에서 해당주소로 실행을 해보니....디버깅 PopUp화면이...^
97김용국3/1/200511317                        답변글 [답변]: 준비되는데로 말씀드리겠습니다 ^^
88안지환2/22/200512857^^ 사이트 잘 들러보았습니다.
89정성태2/22/200512833    답변글 [답변]: ^^ 사이트 잘 들러보았습니다.
85한기열2/22/200511445정성태님 홈같은 부드러운 페이지 넘김?은 어떻게 구현하나요?
86정성태2/22/200512153    답변글 [답변]: 정성태님 홈같은 부드러운 페이지 넘김?은 어떻게 구현하나요? [2]
84김용국2/21/200512225Smart Client에 관한 문의 드립니다.
87정성태2/22/200512289    답변글 [답변]: Smart Client에 관한 문의 드립니다.
... 91  92  93  [94]  95  96  97