성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
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'>닷넷 5에 추가된 POH (Pinned Object Heap)</h1> <p> SOH(Small Object Heap)와 LOH(Large Object Heap)에 더해 .NET 5부터는 Pinned 개체만 전용으로 담는 Heap이 추가되었다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Internals of the POH ; <a target='tab' href='https://devblogs.microsoft.com/dotnet/internals-of-the-poh/'>https://devblogs.microsoft.com/dotnet/internals-of-the-poh/</a> </pre> <br /> 위의 설명만 보면, POH에 어떻게 개체를 할당해야 하는지 알 수 없습니다. 사실, 그동안 알려진 방법을 보면 fixed와 GCHandle 정도가 있는데 그것들은 이미 기존 SOH/LOH에 할당된 개체를 지정해서 pinning하는 방법을 제공할 뿐입니다. 그렇다면 혹시, pinning하는 순간 POH로 복사되는 (동시에 발생하는 overhead까지도 감수하는) 걸까요? 이를 테스트하기 위해 다음과 같이 코드를 작성해 보면,<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.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] class Program { int _n = 5; static unsafe void Main(string[] args) { Program pg = new Program(); { IntPtr ptr = <a target='tab' href='https://www.sysnet.pe.kr/2/0/12487'>GetRefAddress</a>(pg); Console.WriteLine(ptr.ToInt64().ToString("x")); } // fixed로 Pinning fixed (int* p = &pg._n) { IntPtr ptr = new IntPtr(p); Console.WriteLine(ptr.ToInt64().ToString("x")); } // GCHandle로 Pinning { GCHandle handle = GCHandle.Alloc(pg, GCHandleType.Pinned); Console.WriteLine(handle.AddrOfPinnedObject().ToInt64().ToString("x")); Console.WriteLine(handle); } } private unsafe static IntPtr GetRefAddress(object obj) { TypedReference refA = __makeref(obj); return **(IntPtr**)&refA; } } /* 출력결과 297314cb578 297314cb580 297314cb580 */ </pre> <br /> pinning으로 인한 주소가 크게 벗어나지 않는 걸로 봐서 별도의 POH로 이동한 것 같지는 않습니다. 다시 말해, 이것은 POH를 추가했다고 해서 기존 작성한 코드에 어떤 영향이 있는 것은 아님을 의미합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 사용 방법을 찾기 위해 검색했더니 POH에 대해 더 실질적으로 설명하는 글이 나옵니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Pinned Object Heap in .NET 5 ; <a target='tab' href='https://tooslowexception.com/pinned-object-heap-in-net-5/'>https://tooslowexception.com/pinned-object-heap-in-net-5/</a> Pinned Heap ; <a target='tab' href='https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/PinnedHeap.md'>https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/PinnedHeap.md</a> </pre> <br /> 아하... POH 힙을 활용하기 위해 .NET 5부터 새롭게 GC.AllocateArray 메서드를 제공하고 있군요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > GC.AllocateArray<T>(Int32, Boolean) Method ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.gc.allocatearray'>https://learn.microsoft.com/en-us/dotnet/api/system.gc.allocatearray</a> </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;' > // C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.3\System.Private.CoreLib.dll // System.GC public static T[] AllocateArray<[Nullable(2)] T>(int length, bool pinned = false) { GC.GC_ALLOC_FLAGS flags = GC.GC_ALLOC_FLAGS.GC_ALLOC_NO_FLAGS; if (<span style='color: blue; font-weight: bold'>pinned</span>) { if (<span style='color: blue; font-weight: bold'>RuntimeHelpers.IsReferenceOrContainsReferences<T>()</span>) { ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); } flags = GC.GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP; } return Unsafe.As<T[]>(GC.AllocateNewArray(typeof(T[]).TypeHandle.Value, length, flags)); } [MethodImpl(MethodImplOptions.InternalCall)] internal static extern Array AllocateNewArray(IntPtr typeHandle, int length, GC.GC_ALLOC_FLAGS flags); </pre> <br /> pinned == true인 경우 RuntimeHelpers.IsReferenceOrContainsReferences 메서드를 호출해 검사하고 있는데요, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > RuntimeHelpers.IsReferenceOrContainsReferences<T> Method ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.runtimehelpers.isreferenceorcontainsreferences'>https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.runtimehelpers.isreferenceorcontainsreferences</a> </pre> <br /> 메서드의 이름에서도 유추할 수 있지만 순수 blittable 타입인지를 판단하는 역할을 합니다. 그러고 보니 지난 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - GC 힙이 아닌 Native 힙에 인스턴스 생성 - 0SuperComicLib.LowLevel 라이브러리 소개 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12538'>https://www.sysnet.pe.kr/2/0/12538</a> </pre> <br /> NativeClass.InitObj로 할당할 수 있는 개체의 조건으로 제네릭의 unmanaged 제약을 만족할 수 있어야 한다고 했는데, 바로 그 조건을 테스트할 수 있는 방법을 (.NET Core 2.0부터) 메서드로도 제공하고 있었던 것입니다.<br /> <br /> 그리고, GC.AllocateArray도 역시 (NativeClass.InitObj와 유사하게) pinning 해야 하는 개체라면 blittable 타입에 한해서 허용한다는 공통점이 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> POH에 개체를 할당하는 방법은 설명했고, 그렇다면 그게 어떤 의미가 있을까요? 이에 대해서는 "<a target='tab' href='https://devblogs.microsoft.com/dotnet/internals-of-the-poh/'>Internals of the POH</a>" 글에서 이미 잘 설명하고 있습니다.<br /> <br /> 기존에도 개체를 fixed와 GCHandle로 pinning을 했는데, 그중에서 fixed의 경우에는 지정된 block이 확실하므로 보통은 짧게 pin/unpin이 되어 GC 구동 시 크게 부담이 없었습니다. 반면 GCHandle로 pinning하는 경우에는 GCHandle.Free를 하기 전까지는 메모리 고정이 해제되지 않으므로 장시간 SOH/LOH에 점유될 수 있고 특정 조건에서 GC의 메모리 축소(compacting)를 방해해 힙의 파편화를 증가시키며 GC 효율을 낮추게 됩니다.<br /> <br /> 그런데, 따지고 보면 일반 개발자 입장에서 - 이 글을 읽고 있는 여러분 중에 GCHandle 사용을 얼마나 해 보셨는지 묻고 싶군요. ^^ 아마 거의 사용해 본 적이 없을 것이므로 POH가 추가되었다고 해서 뭔가 극적인 성능 향상을 기대할 수 있는 여지가 많지 않습니다. 물론, Win32 API 등을 자주 호출한다면 pinning을 CLR 내부에서 자주 하겠지만 엄밀히 그 정도는 fixed와 유사하게 단기적으로만 점유하는 것에 불과하므로 마찬가지로 GC를 크게 방해하지는 않습니다.<br /> <br /> 그럼에도 불구하고, 이것이 유용한 사례가 있습니다. "<a target='tab' href='https://tooslowexception.com/pinned-object-heap-in-net-5/'>Pinned Object Heap in .NET 5</a>" 글의 작성자는 마지막에 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12480'>ArrayPool</a>의 버퍼로 사용할 것이라고 마무리하고 있습니다. 또한 이와 유사하게 Kestrel의 MemoryPool에 POH를 사용한 것이 POH의 사용 예라고 언급하고 있습니다.<br /> <br /> 정리해 보면, 그동안 pinning을 한 번도 사용해 본 적이 없는 분이라면 그냥 자신이 사용하고 있는 하부 framework에서의 성능 향상을 기대하면서 기존처럼 프로그램하시면 되겠습니다. 여기에 개인적인 의견을 덧붙이면, 근래 들어 C# 언어에서도 나타나는 경향이지만 아마도 이것은 마이크로소프트 내부에서 어떻게든지 ASP.NET Core의 벤치마크 수치를 좀 더 높이기 위한 마이크로 튜닝의 산물이 아닌가 생각됩니다. ^^<br /> <br /> (혹시 POH의 사용으로 인한 성능 향상의 벤치마크 사례가 있다면 덧글 부탁드립니다. ^^)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2150
(왼쪽의 숫자를 입력해야 합니다.)