성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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'>.NET 5+ 환경에서 P/Invoke의 성능을 높이기 위한 SuppressGCTransition 특성</h1> <p> 오호~~~ 재미있는 트윗이 하나 올라왔군요. ^^<br /> <blockquote class="twitter-tweet"><p lang="en" dir="ltr">While working on <a href="https://twitter.com/hashtag/progc2?src=hash&ref_src=twsrc%5Etfw">#progc2</a>, I did some research on the SuppressGCTransition attribute. It magically speeds up p/invokes, with some pretty big drawbacks. If you want to learn more than you will ever need about that attribute, check my article: <a href="https://t.co/xsedceHoDf">https://t.co/xsedceHoDf</a><a href="https://twitter.com/hashtag/dotnet?src=hash&ref_src=twsrc%5Etfw">#dotnet</a></p>— Kevin Gosse (@KooKiz) <a href="https://twitter.com/KooKiz/status/1692127734854459426?ref_src=twsrc%5Etfw">August 17, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...I did some research on the SuppressGCTransition attribute.... ; <a target='tab' href='https://twitter.com/KooKiz/status/1692127734854459426?s=20'>https://twitter.com/KooKiz/status/1692127734854459426?s=20</a> SuppressGCTransition ; <a target='tab' href='https://minidump.net/suppressgctransition-b9a8a774edbd'>https://minidump.net/suppressgctransition-b9a8a774edbd</a> </pre> <br /> 위의 글을 정리해 볼까요? ^^<br /> <br /> <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.suppressgctransitionattribute'>SuppressGCTransition 특성</a>은 .NET 5부터 추가된 것으로, DllImport를 적용한 P/Inovke API에 함께 적용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [DllImport("NativeLib.dll")] <span style='color: blue; font-weight: bold'>[SuppressGCTransition]</span> static extern int Increment(int value); </pre> <br /> 이것을 적용하면 (저자가 작성한 테스트 함수의 경우) 4배 정도의 속도 향상이 있었다고 하는데요, 그럴 수밖에 없는 것이 해당 특성으로 인해 P/Invoke의 Transition 관련 코드들이 없어지기 때문입니다. 이에 대해서는 전에 몇 번 언급한 적이 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > P/Invoke의 성능을 높이기 위해 C++/CLI가 선택되려면? ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1229'>https://www.sysnet.pe.kr/2/0/1229</a> windbg - C# PInvoke 호출 시 마샬링을 담당하는 함수 분석 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12065'>https://www.sysnet.pe.kr/2/0/12065</a> </pre> <br /> 하지만, SuppressGCTransition이 항상 좋은 것은 아닌데요, <a target='tab' href='https://minidump.net/suppressgctransition-b9a8a774edbd'>원문의 글</a>에서도 나오듯이 P/Invoke API의 실행 시간을 일부러 100ms 지연시킨 경우에는 반대로 2배의 속도 저하가 생깁니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 속도 저하의 원인을 설명하기 위해서는 스레드의 2가지 상태, Preemptive와 Cooperative를 알아야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET Thread 상태가 Cooperative일 때 GC hang 현상 재현 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11634'>https://www.sysnet.pe.kr/2/0/11634</a> </pre> <br /> 닷넷 세계에서, P/Invoke를 통해 진입한 비-관리 코드는 GC에 방해를 하지 않는 Preemptive 상태입니다. 이러한 상태 변화는 P/Invoke 대상이 되는 API의 앞/뒤로 실행되는 Transition 관리 코드에서 처리하는데요, 대충 이런 식입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[닷넷 코드를 수행 중인 Cooperative 상태의 스레드가 P/Invoke 호출]... [transition - 스레드를 Preemptive 상태로 마킹] Native API 호출 [transition - 스레드를 Cooperative 상태로 마킹] ...[닷넷 코드를 계속 수행(Cooperative 상태)]... </pre> <br /> 하지만 SuppressGCTransition 특성을 지정하면 transition 코드가 없어지기 때문에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[닷넷 코드를 수행 중인 Cooperative 상태의 스레드가 P/Invoke 호출]... Native API 호출 (여전히 Cooperative 상태) ...[닷넷 코드를 계속 수행(Cooperative 상태)]... </pre> <br /> Native API 실행 중에도 Cooperative로 남게 되는 것입니다.<br /> <br /> 저런 변화를 GC에 엮어서 설명해 볼까요?<br /> <br /> GC는, Preemptive 상태의 스레드는 무시하고 수행할 수 있습니다. P/Invoke의 상황을 예로 들면, 일단 Preemptive로 표시된 후 Native API가 호출되는 동안에 GC는 그 스레드와 병렬로 수행될 수 있습니다. 그렇다면 GC가 수행되는 사이 Native API 호출이 완료되고 Cooperative 모드의 닷넷 코드 영역으로 넘어가면 문제가 되지 않을까요? ^^<br /> <br /> 그럴 수가 없는 것이, Native API 호출 이후 현재 GC가 수행 중이면 닷넷 코드로 진입하기 전에 blocking을 하기 때문입니다.<br /> <br /> 자, 그럼 SuppressGCTransition 특성이 왜 성능에 영향을 미치는지 예상할 수 있습니다. Native API 전/후의 transition 코드를 없애기 때문에 스레드는 계속 Cooperative 상태로 머물게 되고, 이때 GC는 그 스레드와 병렬로 수행할 수 없습니다. 결국 GC는 Cooperative 스레드를 안전한 위치의 코드를 수행 중인 지 확인하기 위해 suspend 시켜, 1) 만약 안전한 영역이라면 그대로 (suspend 상태로) GC를 수행하지만, 2) 안전하지 않은 영역을 수행 중이면 안전한 영역으로 나올 때까지 다시 resume 시킨 후 GC는 "대기"를 하게 됩니다.<br /> <br /> 정리하면, SuppressGCTransition으로 인해 GC는 모든 .NET 스레드를 중지시킨 체로 대기하다가 P/Invoke API의 호출이 완료돼 해당 스레드가 중지되어서야 비로소 GC 작업을 하기 때문에 성능에 영향을 미치게 되는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> SuppressGCTransition 적용의 또 다른 부작용은 디버거가 정상적으로 P/Invoke로 들어선 스레드의 호출 스택을 보여줄 수 없다는 것입니다. 아마도 이것은 차차 개선되지 않을까 싶은데요, 현재의 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/core/diagnostics/sos-debugging-extension'>SOS Debugger 확장</a>은 Transition이 당연히 있는 것으로 가정하고 !clrstack의 결과를 찾기 때문에 SuppressGCTransition이 있는 P/Invoke 호출에 대해서는 정상적인 풀이를 못하는 듯합니다. (순전히 제 의견입니다. ^^)<br /> <br /> <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.suppressgctransitionattribute#remarks'>문서에 따르면</a>, P/Invoke로 호출하는 native 함수 안에서 예외를 던져서도 안 된다고 합니다. <a target='tab' href='https://minidump.net/suppressgctransition-b9a8a774edbd'>원문의 저자</a>가 수행한 테스트로는 별다른 부작용을 발견할 수는 없었지만, 아무튼 문서에 따르는 것이 좋겠다는 의견입니다.<br /> <br /> 또한, P/Invoke 내에서 닷넷 측으로의 콜백(Reverse P/Invokes)도 해서는 안 된다고 합니다. 이것은 Transition 층이 없어지는 것으로 인한 부작용인데요, 예를 들어 기존에는 다음과 같은 호출과 스레드 상태 변경이 있었는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // SuppressGCTransition 미지정 .net code -> (transition) -> <span style='color: blue; font-weight: bold'>p/invoke code</span> -> (transition) -> (callback to) .net code -> (transition) -> <span style='color: blue; font-weight: bold'>p/invoke code</span> [Cooperative] -> <span style='color: blue; font-weight: bold'>[Preemptive]</span> -> [Cooperative] -> <span style='color: blue; font-weight: bold'>[Preemptive]</span> </pre> <br /> 보는 바와 같이 p/invoke에서 콜백을 호출하기 전/후 동일하게 [Preemptive] 상태로 일치합니다. 반면 SuppressGCTransition으로 transition 코드가 빠진 경우에는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // SuppressGCTransition 지정 .net code -> <span style='color: blue; font-weight: bold'>p/invoke code</span> -> (transition) -> (callback to) .net code -> (transition) -> <span style='color: blue; font-weight: bold'>p/invoke code</span> <span style='color: blue; font-weight: bold'>[Cooperative]</span> -> [Cooperative] -> <span style='color: blue; font-weight: bold'>[Preemptive]</span> </pre> <br /> 콜백 호출 전에는 Cooperative 모드였는데, 콜백을 완료하고 나서는 Preemptive 모드로 바뀌었습니다. 따라서 GC로 하여금 안전한 상태로 판정받기 때문에 별다른 조치가 없는 상태에서 P/Invoke 호출을 빠져나와 곧바로 .NET Code를 수행하는 시점에 자칫 (아마도) ExecutionEngineException 오류가 발생할 수 있습니다.<br /> <br /> 기타 "Stack spill"에 대해서도 설명하지만 어차피 이것은 Transition 코드가 갖는 오버헤드이므로 딱히 중요한 것은 아닙니다.<br /> <br /> 마지막으로, 문서에는 SuppressGCTransition에 대한 사용 지침을 알려주고 있으니 참고하시고,<br /> <br /> <ul> <li>가능한 1us(1마이크로 초) 안에 수행될 함수에 대해서만 SuppressGCTransition 지정 (권장)</li> <li>닷넷 코드로의 callback이 없어야 하고 (필수),</li> <li>예외를 발생시켜서는 안 되고 (필수),</li> <li>(블록킹을 발생할 수 있는) lock을 다루지 않아야 함 (권장)</li> </ul> <br /> 덕분에 재미있는 것을 알게 되었습니다. ^^<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1754
(왼쪽의 숫자를 입력해야 합니다.)