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

... 31  32  33  34  35  36  37  [38]  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
4906guest10/25/20179067.net core, .net standard 관련해서 궁금합니다 [2]
490510/25/20178339watermark 관련문의 [1]
4903Ho10/25/20178739간단한 서버를 구현해 보고 싶습니다. [2]
4902황재승10/22/20179772C# 7.1 프로그래밍 왕초보자도 이해할 수 있는 수준인가요? [1]
4901김레오10/17/201714473c# 프로그램 난독화 도구인 confuserex라는 도구를 사용하다 여쭙습니다. [2]
4900황준범10/12/201710228ClickOnce 배포관련 질문드립니다. [1]
4899Ques...9/28/201712647C# 프로그램이 "응답 없음" 시에도 계속 독립적으로 돌아가는 타이머 생성법 [3]
4898ssdrm9/27/20179437Clickonce 실행시 보안에 막힙니다 ㅠ [1]
4897김태진9/23/20179612윈도우7 작업관리자의 상세(details)탭 생성에 대해 여쭙습니다. [1]
4896장진국9/19/201710962안녕하세요 WPF 에서 Window객체가 가비지 콜렉션에 의해 수집되지 않는거 같아서 문의드립니다. [1]
4895Ques...9/18/201710620Generic 에 관하여 질문드립니다. [5]
4894얼마전6...9/14/201712504C# 7.1에서 보강된 부분만 PDF로 제공하는 건 아니되나요? [2]
4893BigII9/14/201711544타 언어(JAVA, PHP 등)에서 받은 RSA 개인키 문자열을 이용하여 내용 복호화 가능 여부 [4]
4892Ques...9/13/201710577서브 폼에서는 무거운 작업을해도 속도가 빠를까요 ?? [1]
4891윤현수9/11/201711239Taskbar에 관한 질문입니다. [5]
4890제발9/11/20179768 시작하세요! C# 6.0 프로그래밍 책이 절판됐나요? [5]파일 다운로드1
4889낙낙이9/7/20179196안녕하세요! xsl관련 이야기입니다. [1]
4888heyh...9/6/20179025클릭원스를 수동으로 배포 시 업데이트 할 때 [1]
4887이경현9/4/20179586Windows server 2012 파일 없어짐 현상... [1]
4886질문자9/1/201711452disconnecteditem에 대하여 아시나요? [4]파일 다운로드1
4883솔솔8/30/20179938dataview에서 select한 index 가져오기 [1]
4882user8/30/201712597UI 변경 작업 여러개를 동시에 사용하려면 어떻게 해야되나요 ?? [7]
4884user8/31/20179237    답변글 [답변]: UI 변경 작업 여러개를 동시에 사용하려면 어떻게 해야되나요 ??파일 다운로드1
4881kmi8/30/20179420진행속도가 중간에 더뎌지는 문제가 있는데 해결할 수 있는 방법이 있나 궁금합니다. [3]
4880김호종8/29/20179833HTTP JSON POST 관련 질문 드립니다. [1]
4879aos8/27/201710012DateTime 표시할 때 포맷 설정시 밀리초까지 포함하는 법이 궁금합니다 [1]
... 31  32  33  34  35  36  37  [38]  39  40  41  42  43  44  45  ...