성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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'>P/Invoke의 성능을 높이기 위해 C++/CLI가 선택되려면?</h1> <p> 예제 코드로 직접 설명해 볼까요? C#의 경우, 다음과 같이 P/Invoke 형식으로 Win32 API를 호출할 수 있는 반면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class Program { [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId(); static void Main(string[] args) { GetCurrentThreadId(); } } </pre> <br /> C++/CLI의 경우에는 별다르게 P/Invoke 절차 없이 일반적인 C/C++에서 했던 것처럼 Win32 API를 곧바로 호출할 수가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #pragma once #include <Windows.h> using namespace System; namespace TestLibrary { public ref class Class1 { public: int GetId() { return ::GetCurrentThreadId(); } }; } </pre> <br /> C++/CLI의 활용예 중에 하나가, Managed 언어(C#,VB.NET,...)에서 P/Invoke를 이용하여 Win32 DLL에서 제공하는 API를 호출할 때 성능을 높이려는 방안이 하나 있습니다. 실제로 MSDN 문서에 보면 다음과 같은 이야기가 실려 있는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Performance Considerations for Interop (C++) ; <a target='tab' href='https://learn.microsoft.com/en-us/cpp/dotnet/performance-considerations-for-interop-cpp'>https://learn.microsoft.com/en-us/cpp/dotnet/performance-considerations-for-interop-cpp</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> <span style='color: blue; font-weight: bold'>C++ Interop uses the fastest possible method of data marshaling</span>, whereas P/Invoke uses the most robust method. This means that C++ Interop (in a fashion typical for C++) provides optimal performance by default, and the programmer is responsible for addressing cases where this behavior is not safe or appropriate. </div><br /> <br /> 아울러, Managed 응용 프로그램에서 Native API를 호출하려면 다음과 같은 작업들이 필요하다고 되어 있습니다.<br /> <br /> <ul> <li>The function call arguments are marshaled from CLR to native types.</li> <li>A managed-to-unmanaged thunk is executed.</li> <li>The unmanaged function is called (using the native versions of the arguments).</li> <li>An unmanaged-to-managed thunk is executed.</li> <li>The return type and any "out" or "in,out" arguments are marshaled from native to CLR types.</li> </ul> <br /> 여기서 우리가 정확하게 알아야 할 것은, C++/CLI 역시 Native API를 호출할 때에는 C#에서 PInvoke를 통해 호출했을 때처럼 managed-to-unmanaged thunk / unmanaged-to-managed thunk는 필요하다는 점입니다. 참고로, "thunk"에 대해서 낯설어 하시는 분들이 계실 텐데... 음... Visual Studio로 디버깅을 하다 보면 다음과 같은 "Call Stack"을 보는 경우가 있는데요.<br /> <br /> <img alt='pinvoke_perf_1.png' src='/SysWebRes/bbs/pinvoke_perf_1.png' /><br /> <br /> 위에서 보이는 "[Managed to Native Transition]"에 해당하는 부분이 바로 "thunk"입니다. (물론, thunk라는 용어는 다양하게 쓰이는데, 일례로 WOW64에서도 64비트와 32비트 간의 변환을 위해 쓰이는 부분도 thunk라고 부릅니다.)<br /> <br /> 이렇게 따지면, C++/CLI에서 유일하게 성능을 높일 수 있는 부분은 바로 "data marshaling"입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 개인적으로, 이 부분에서 다소 궁금한 점이 생겼습니다. 그렇다면, 데이터 마샬링 부하 없이 실제로 어느 정도의 성능 차이가 있을까 하는 것입니다.<br /> <br /> 당연히... 이런 것은 테스트를 해봐야겠지요. ^^<br /> <br /> 그래서, 2개의 프로젝트를 준비하고,<br /> <br /> <ul> <li>ConsoleApplication1: C# Console EXE</li> <li>TestLibrary: C++/CLI Library</li> </ul> <br /> C#에서 다음과 같이 PInvoke 호출을 하는 코드를 만든 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class Program { <span style='color: blue; font-weight: bold'>[DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();</span> static void Main(string[] args) { int count = 100000; PInvokeTest(1); // JIT 컴파일 시간을 측정 시간에서 없애기 위해 미리 한번 호출 Console.WriteLine("==================="); PInvokeTest(count); } private static void PInvokeTest(int loopCount) { Stopwatch st = new Stopwatch(); st.Start(); <span style='color: blue; font-weight: bold'>for (int i = 0; i < loopCount; i++) { GetCurrentThreadId(); }</span> st.Stop(); Console.WriteLine(st.ElapsedTicks); } } </pre> <br /> 역시 C++/CLI에서도 위의 기능을 하는 코드를 만들어서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #pragma once #include <Windows.h> using namespace System; namespace TestLibrary { public ref class Class1 { public: void GetId2(int count) { for (int i = 0; i < count; i ++) { ::GetCurrentThreadId(); } } }; } </pre> <br /> 동일하게 시간을 측정할 수 있도록 C# Main 함수에 넣어두었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { int count = 100000; PInvokeTest(1); <span style='color: blue; font-weight: bold'>Cppcli2Test(1);</span> Console.WriteLine("==================="); PInvokeTest(count); <span style='color: blue; font-weight: bold'>Cppcli2Test(count);</span> } private static void Cppcli2Test(int count) { TestLibrary.Class1 cl = new TestLibrary.Class1(); Stopwatch st = new Stopwatch(); <span style='color: blue; font-weight: bold'> st.Start(); cl.GetId2(count); st.Stop();</span> Console.WriteLine(st.ElapsedTicks); } </pre> <br /> 다음은 3회 실행한 결과입니다.<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: 12013, 9385, 11388 C++/CLI : 9346, 9330, 10504 </pre> <br /> 보시는 것처럼 거의 차이가 나지 않습니다. 10만 번 수행에 저 정도 차이면, 추가된 C/C++ 프로젝트 관리나 그로 인한 x86/x64 DLL 파일의 유지/보수 노력을 감안했을 때 하지 않는 편이 더 낫습니다.<br /> <br /> 물론, 데이터 마샬링이 어느 정도 요구되는 상황에서는 분명히 C++/CLI의 장점이 있을 테지만, 우리가 일반적으로 만드는 C# 응용 프로그램에서는 사실상 P/Invoke의 성능을 높이기 위해 굳이 C++/CLI를 사용할 필요는 없다는 것을 잘 보여주고 있습니다.<br /> <br /> 웹에서 검색해 보니, 한 가지 실험 결과가 더 눈에 띄는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > PInvoke Performance ; <a target='tab' href='http://www.codeproject.com/Articles/253444/PInvoke-Performance'>http://www.codeproject.com/Articles/253444/PInvoke-Performance</a> </pre> <br /> 위의 글에서 재미있는 결과를 볼 수 있는데, 바로 Native API의 속도가 눈에 띄게 높다는 점입니다.<br /> <br /> 아하~~~ 그렇다면, 제 결론은 이렇습니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> 그나마라도 성능이 아쉬운 상황이어서 어떻게라도 개선을 해야 한다면, C++/CLI를 사용하기보다는 아예 Win32 DLL(또는 COM 개체)을 만들어서 C/C++ 수준에서 성능을 높이는 것이 더 좋은 선택입니다!<br /> </div><br /> <br /> * <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=688&boardid=331301885'>첨부 파일은 다음의 3개 프로젝트를 포함</a>하고 있습니다.<br /> <br /> <ul> <li>ConsoleApplication1: C# 콘솔 응용 프로그램</li> <li>TestLibrary: C++/CLI 라이브러리</li> <li>TestWin32DLL: C/C++ Win32 DLL</li> </ul> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
8774
(왼쪽의 숫자를 입력해야 합니다.)