Microsoft MVP성태의 닷넷 이야기
string.Join()과 Enumerable.Aggregate()의 차이가 궁금합니다. [링크 복사], [링크+제목 복사],
조회: 10644
글쓴 사람
이성환 (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]

... 46  47  48  49  50  51  52  53  54  55  56  57  58  [59]  60  ...
NoWriterDateCnt.TitleFile(s)
1151황기동6/5/201311369무선 패킷 전송관련 질문 [1]
1147이진권6/2/201311378c# 윈폼 facebook과 연동2 [1]
1144황은영5/29/201315888원격지 서버에서 COM+ 호출이 되지 않습니다. [7]
1143김선희5/28/201316812윈도우 8, 익스플로러 10, 64bit->32bit [1]
1142백지훈5/15/201311234음성인식 TTS 관련해서 질문드립니다 ㅜㅜ [2]파일 다운로드1
1141나그네5/10/201311300안녕하세요..전자세금 관련하여.. 보다보다 여기까지 왔습니다. [4]
1140이성환5/8/201311488제네릭과 배열 관련 기초 질문입니다. [11]
1139양영석5/3/201310058페이스북 로그인 관련하여.... [1]
1137김민현4/30/201310114Binary 구조체에 대한 Linq query 관련 질문입니다. [3]파일 다운로드1
1136박상영4/25/201310978ksc5601에서 지원하지않는 확장문자 찾는방법 [3]
1135sequ...4/16/201326562c# dll c++ 에서 사용하기 (event 사용) [17]
1134이성환4/12/201310762volatile 키워드 관련 질문입니다. [3]파일 다운로드1
1133서광석4/8/201313956vb.net 2010에서 배포시 문제점 확인 방법에 대한 질문드립니다 [1]
1131헤딩3/28/201316251IIS 처리 성능을 높이고 싶으면 풀의 proecss 수를 계속 늘리면 될까요? [1]
1130박재율3/27/201310530WCF 서비스가 종종 죽는데 도움좀 부탁드립니다 ㅠ [1]
1129이상민3/21/201314894안녕하세요...IE Embedded Smart Client가 Windows8에서 정상동작을 하지 않습니다. [8]
1125박홍균3/19/20139079안녕하십니까. visual studio 에서 TFS 접속 관련으로 궁금한 점 여쭤봅니다.
1124궁금이3/18/201312116IIS 서버 이중화 구성시 데이터 베이스 트랜잭션 관리에 관해서... [1]
1127궁금이3/19/20139525    답변글 [답변]: IIS 서버 이중화 구성시 데이터 베이스 트랜잭션 관리에 관해서...(정성태님 봐주세요)
1122김시현3/11/201310948 Xamarin 에 관련하여 질문드립니다. [4]
1121롬메달2/14/201311046Assembly.Load로 얻은 객체안에서 제3의 어셈블리를 참조할때의 문제입니다. [8]
1120Lyn2/12/201310567안녕하세요. 블로그에 기술관련글을 꾸준히 포스팅 하실 수 있는 원동력이 궁금합니다. [1]
1119박진영2/4/201334060닷넷 DLL 실행 에러 (보호된 메모리를 읽거나 쓰려고 했습니다..) [2]파일 다운로드1
1118C#조으다1/29/201312132공유기 관리 페이지에 접근시 윈도우 인증창이 뜹니다. [4]
1117최윤호1/28/20139904답변주셔서 감사합니다 질문의 요지는 그게 아니고 [1]파일 다운로드1
1116C#조으다1/27/201317121비프음을 컴퓨터 내장 스피커로 출력하려면.. [2]
... 46  47  48  49  50  51  52  53  54  55  56  57  58  [59]  60  ...