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

... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
5125게스트2/19/20196938delegate를 활용한 event 를 적절히 불러오고 싶습니다. [2]
5124정근화2/12/20196814윈도우 서버2003 환경 오류 [2]
5123김주현2/8/20196849MS LUIS 에 대한 소개 하실 계획이 있으신가요? [1]
5122jaka...2/1/20198694Clickonce 배포 후 Command 실행 [2]파일 다운로드1
5121엔벌이1/31/20198114C# DataGridView의 MDB파일 함수? ArrayList? [1]파일 다운로드1
5120임우진1/30/20198610웹에서 응용프로그램 바로 실행하기 관련 브라우저에서 파라미터가 넘어오지 않습니다.ㅜㅜ [2]
5119guest1/29/201910004교재에 오탈자 있어 알려드리려 합니다 [1]
5118WPF꿈...1/26/20197929GetHashCode 메서드에 대해서 [1]
5117하주형1/25/20198601List<int>에 대한 이해가 잘안됩니다. [5]
5116게스트1/24/20197352asp.net 관련 gridview webform 질문 드립니다. [1]파일 다운로드1
5115Soul...1/24/20197475투명 패널 질문드립니다. [2]
5114박현일1/20/20197821WPF DataContext 관련 초보 질문을 드려봅니다.^^ [5]
5113하주형1/20/20197120안녕하세요 시작하세요 C# 인코딩관련 질문드립니다. [1]
5112손성배1/19/201916108안녕하세요 cp949 인스톨시 오류입니다... 너무 힘들어요 [5]
5111게스트1/10/20197760암호화 라이센스 관련 문의 드립니다. [1]
5110WPF꿈...1/9/20197405Thread Abort 함수 사용시 [2]
5109닷넷개발1/9/20197375thread 관련 질문 예제.. [2]파일 다운로드1
5108닷넷개발1/9/20198523thread 관련 질문 드립니다.. [4]
5107우코아1/4/201911178WPF에서 로딩중 이미지를 구현 - Project [5]파일 다운로드1
5106우코아1/3/20198812WPF에서 로딩중 이미지를 구현 - Source [1]
5104우코아1/1/201910045WPF에서 로딩중 이미지를 구현 [4]
5103이혜성12/31/20181063432bit .net 으로 만들어진 dll파일 [5]
5102돌고래12/18/20189408자료구조와 알고리즘 도서 관련 질문입니다. [4]
5101세퉁12/17/20187725안녕하세요 wpf 공부중인데 질문있습니다. [4]파일 다운로드1
5100돌고래12/16/20187094도서 추천 부탁드립니다. [1]
5099WPF12/12/201810172안녕하세요. WPF에서 UWP Control을 참조하려고 합니다. [3]파일 다운로드1
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...