성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# 7.2의 특징 - GC 및 메모리 복사 방지를 위한 struct 타입 개선</h1> <p> 지난 글들을 통해 C# 7.2의 struct 관련 문법을 소개했는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 7.2 (1) - readonly 구조체 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11524'>http://www.sysnet.pe.kr/2/0/11524</a> C# 7.2 (2) - 메서드의 매개 변수에 in 변경자 추가 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11525'>http://www.sysnet.pe.kr/2/0/11525</a> 기타 - Microsoft Build 2018 - The future of C# 동영상 내용 정리 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11536'>http://www.sysnet.pe.kr/2/0/11536</a> </pre> <br /> 위에서 소개한 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/11536'>기타 - Microsoft Build 2018 - The future of C# 동영상 내용 정리</a>" 글에서도 나왔지만 C# 7.2의 최대 특징은 GC와 메모리 복사를 줄이는 것에 있습니다. 이를 위해 크게 개선된 점이 바로 "구조체"(와 구조체로 정의된 "Span<T>")입니다.<br /> <br /> 그렇다면, 성능이 어느 정도 체감될까요?<br /> <br /> 예를 들어, 기존에 다음과 같이 작성된 Game Loop가 있다고 가정해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Threading; // .NET Core 2.1 + x64 + Release에서 테스트 namespace ConsoleApp2 { class Program { static void Main(string[] args) { Thread t = new Thread(gcCheck); t.IsBackground = true; t.Start(); GameLoop(); } private static void gcCheck() { long oldValue = 0; while (true) { int gcCount = GC.CollectionCount(0) + GC.CollectionCount(1) + GC.CollectionCount(2); long frame = _frame; double fps = (frame - oldValue) / 60.0; oldValue = frame; Console.WriteLine(gcCount + ": " + fps); Thread.Sleep(1000); } } static int _frame; private static void GameLoop() { while (true) { int x = Environment.TickCount % 30; int y = Environment.TickCount % 35; int z = Environment.TickCount % 60; Vector player = new Vector(x, y, z); x = Environment.TickCount % 30; y = Environment.TickCount % 35; z = Environment.TickCount % 60; Vector speed = new Vector(x, y, z); Vector newPos = player + speed; _frame++; } } } class Vector { double _x; double _y; double _z; public double X { get { return _x; } } public double Y { get { return _y; } } public double Z { get { return _z; } } public Vector(double x, double y, double z) { _x = x; _y = y; _z = z; } public static Vector operator + (Vector v1, Vector v2) { return new Vector(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); } public override string ToString() { return $"({_x},{_y},{_z})"; } } } </pre> <br /> 이때 화면에 출력되는 GC 수와 fps를 보면 다음과 같습니다. (소수점 2자리 이하는 절삭했습니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0: 0 501: 219024.1 1350: 370783.25 2320: 423876.9 3052: 319816.06 3959: 396515.31 4905: 413009.1 5799: 390606.53 6795: 435278.36 7713: 400970.31 8664: 415150.06 9612: 414202.71 ...[생략]... </pre> <br /> 게임에서 GC 발생으로 인한 성능 저하는 가장 피하고 싶은 것 중의 하나일 텐데요, 이를 위해 Vector를 struct로 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct Vector { //...[생략]... } </pre> <br /> 이렇게 바꾸고 실행하면 GC가 발생하지 않으므로 성능이 2배 가까이 올라가는 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0: 0 0: 832359.6 0: 887992.65 0: 894983.23 0: 893596.2 0: 847630.03 0: 884984.05 0: 897776.4 0: 883163.91 0: 911639.2 0: 899867.93 ...[생략]... </pre> <br /> 더 개선할 수 있을까요? struct 인스턴스를 인자로 전달 시 값 복사가 발생하는 부하를 없애기 위해 in 예약어를 operator +에 적용해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct Vector { //...[생략]... public static Vector operator + (<span style='color: blue; font-weight: bold'>in Vector v1, in Vector v2</span>) { return new Vector(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); } } </pre> <br /> 그런데 실제로 해보면 딱히 그다지 나아진 성능을 볼 수 없고 오히려 떨어졌습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0: 0 0: 729735.63 0: 767630.78 0: 748675.51 0: 758570.78 0: 734026.31 0: 733838.71 0: 756316.41 0: 768155.45 0: 761856.18 0: 779781.7 0: 759016.11 0: 769092.55 </pre> <br /> 이유는, v1.X, v2.X 등에서의 get 접근자로 인해 방어 복사본이 생성되었기 때문입니다. 이 부하를 없애기 위해 readonly를 적용해 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>readonly struct</span> Vector { <span style='color: blue; font-weight: bold'>readonly</span> double _x; <span style='color: blue; font-weight: bold'>readonly</span> double _y; <span style='color: blue; font-weight: bold'>readonly</span> double _z; //...[생략]... } </pre> <br /> 다시 성능 테스트를 해보면,... 그래도 거의 달라진 점 없이 struct만 적용했을 때를 따라잡지 못합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0: 0 0: 736243.83 0: 775353.63 0: 753936.2 0: 758081.36 0: 775756.01 0: 707858.68 0: 780843.81 0: 749853.05 0: 774323.53 0: 771705.58 0: 710503.7 </pre> <br /> 혹시 모르니, struct에 포함된 필드를 (기존 3개에서) 6개로 늘려보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct Vector { double _x; double _y; double _z; <span style='color: blue; font-weight: bold'>double _x2; double _y2; double _z2;</span> // ...[생략]... public static Vector operator + (Vector v1, Vector v2) { return new Vector(...[생략]...); } } </pre> <br /> 위와 같이 하면 복사 부하가 늘어날 것으로 짐작되는데, 이럴 때 일반 struct인 경우 아래의 성능을 보이지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0: 0 0: 499835.38 0: 459274.05 0: 503823.2 0: 413757.51 0: 503190.33 0: 494314.35 0: 470810.4 0: 506102.25 0: 489019.13 </pre> <br /> 복사 부하가 없어지는 readonly struct + in 예약어로 바꾸면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > readonly struct Vector { readonly double _x; readonly double _y; readonly double _z; readonly double _x2; readonly double _y2; readonly double _z2; // ...[생략]... public static Vector operator + (in Vector v1, in Vector v2) { return new Vector(...[생략]...); } } </pre> <br /> 이번엔 성능이 조금 나아졌습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0: 0 0: 475020.35 0: 597225.11 0: 549815.65 0: 578523.76 0: 603204.06 0: 616510.96 0: 598533.23 0: 567418.03 0: 601100.78 </pre> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1268&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 현상을 보면, struct의 복사 부하가 낮을 때는 readonly struct + in 예약어 처리가 살짝 느렸습니다. 반면 복사 부하가 늘어나면서 readonly struct + in 예약어 처리가 조금 빨라졌습니다.<br /> <br /> 이런 차이가 나는 이유는, 인라인 메서드 처리에 있습니다. 작은 수의 인자일 때(예제에서는 3개) .NET Core CLR은 일반 struct의 op_Addition 호출을 인라인으로 대체한 반면, readonly struct + in 메서드의 호출은 인라인 시키지 않았습니다. 이 때문에 in 예약어로 인한 값 복사 오버헤드가 없음에도 불구하고 일반적인 struct 구문이 더 빨랐던 것입니다.<br /> <br /> 반면 필드가 6개로 늘어나면서 일반 struct의 op_Addition 호출도 인라인되지 않고 함수 호출로 바뀝니다. 이 때문에 값 복사를 하지 않는 readonly struct + in 메서드의 호출이 더 나은 성능을 보이게 되는 것입니다. 여기서 재미있는 것은, 일반 struct의 op_Addition과 readonly struct + in일 때의 op_Addition 메서드의 IL 코드는 Readonly 특성을 제외하고 완전히 같다는 것입니다.<br /> <br /> 따라서 다음과 같이 옵션을 주면 readonly struct + in의 op_Addition 메서드에 대해서도 인라인 처리가 정상적으로 이뤄집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>[MethodImpl(MethodImplOptions.AggressiveInlining)]</span> public static Vector operator +(in Vector v1, in Vector v2) { return new Vector(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); } </pre> <br /> 이때의 성능 차이는 일반 struct의 op_Addition과 비슷합니다. 아마도 readonly struct + in 메서드의 기본 인라인 처리는 CoreCLR 버전이 올라가면서 바뀔 수도 있을 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그럼 정리해 볼까요? ^^<br /> <br /> 어찌 보면 readonly struct + in 처리가 성능을 높인 것이라기보다는, 최근의 함수형 프로그래밍 추세에 따라 immutable 타입 정의가 늘어나면서 발생하는 방어본 복사로 인한 성능 저하를 보완하는 형식으로 나왔다고도 볼 수 있습니다. 어쨌든, 결과적으로는 struct의 최대 단점이었던 복사 오버 헤드를 피할 수 있는 방법이 나왔고 그에 따라 스택의 부담이 줄어들면서 이전과는 다르게 보다 더 적극적으로 "값 형식"을 활용할만한 여지가 생긴 것입니다.<br /> <br /> 이러한 struct 관련 개선과 비-관리 메모리에 대한 Span 래퍼를 극단적으로 이용하게 되면 GC가 거의 발생하지 않는 닷넷 응용 프로그램을 만드는 것이 가능합니다. 이로 인해 게임 루프나 Request/Response 유형의 웹 기반 프레임워크에서는 부하를 최소화시켜 응용 프로그램의 성능을 보다 더 높일 수 있게 되었습니다. 그 좋은 사례로, .NET Core 스스로 이런 부분들을 적용해 성능 개선을 이뤄낸 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Performance Improvements in .NET Core 2.1 ; <a target='tab' href='https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-2-1/'>https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-2-1/</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
7611
(왼쪽의 숫자를 입력해야 합니다.)