성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <br /> <div style='font-family: 맑은 고딕, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>닷넷 프로파일러 - IL 코드 재작성</div><br /> <br /> 지난번에, 프로파일러가 제대로 응용이 된 예를 하나 소개해드렸지요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > 닷넷 프로파일러의 또 다른 응용: Visual Studio 2010 Historical Debugging ; <a target='_tab' href='/2/0/759'>http://www.sysnet.pe.kr/2/0/759</a> </pre> <br /> 이에 대해서 좀 더 알아볼까요?<br /> <br /> 일반적으로 닷넷 프로파일러는 느리다는 인식이 있습니다. 사실, "Historical Debugging" 기능 역시 켜 놓고 있으면 최초 디버깅 시작 시에 속도가 눈에 띌 정도로 느립니다.<br /> <br /> 하지만, "모든 프로파일러"가 이와 같이 느린 것은 아니고, 경우에 따라서는 보통의 응용 프로그램 속도와 유사할 수 있습니다. 왜냐하면, 프로파일러는 다음과 같은 종류의 콜백을 취사선택해서 받을 수 있으며 이 중에서 특정 기능만이 응용 프로그램 속도를 느리게 만들기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > enum __MIDL___MIDL_itf_corprof_0000_0002 { COR_PRF_MONITOR_NONE = 0, COR_PRF_MONITOR_FUNCTION_UNLOADS = 0x1, COR_PRF_MONITOR_CLASS_LOADS = 0x2, COR_PRF_MONITOR_MODULE_LOADS = 0x4, COR_PRF_MONITOR_ASSEMBLY_LOADS = 0x8, COR_PRF_MONITOR_APPDOMAIN_LOADS = 0x10, COR_PRF_MONITOR_JIT_COMPILATION = 0x20, COR_PRF_MONITOR_EXCEPTIONS = 0x40, COR_PRF_MONITOR_GC = 0x80, COR_PRF_MONITOR_OBJECT_ALLOCATED = 0x100, COR_PRF_MONITOR_THREADS = 0x200, COR_PRF_MONITOR_REMOTING = 0x400, COR_PRF_MONITOR_CODE_TRANSITIONS = 0x800, COR_PRF_MONITOR_ENTERLEAVE = 0x1000, COR_PRF_MONITOR_CCW = 0x2000, COR_PRF_MONITOR_REMOTING_COOKIE = 0x4000 | COR_PRF_MONITOR_REMOTING, COR_PRF_MONITOR_REMOTING_ASYNC = 0x8000 | COR_PRF_MONITOR_REMOTING, COR_PRF_MONITOR_SUSPENDS = 0x10000, COR_PRF_MONITOR_CACHE_SEARCHES = 0x20000, COR_PRF_MONITOR_CLR_EXCEPTIONS = 0x1000000, COR_PRF_MONITOR_ALL = 0x107ffff, COR_PRF_ENABLE_REJIT = 0x40000, COR_PRF_ENABLE_INPROC_DEBUGGING = 0x80000, COR_PRF_ENABLE_JIT_MAPS = 0x100000, COR_PRF_DISABLE_INLINING = 0x200000, COR_PRF_DISABLE_OPTIMIZATIONS = 0x400000, COR_PRF_ENABLE_OBJECT_ALLOCATED = 0x800000, COR_PRF_ENABLE_FUNCTION_ARGS = 0x2000000, COR_PRF_ENABLE_FUNCTION_RETVAL = 0x4000000, COR_PRF_ENABLE_FRAME_INFO = 0x8000000, COR_PRF_ENABLE_STACK_SNAPSHOT = 0x10000000, COR_PRF_USE_PROFILE_IMAGES = 0x20000000, } COR_PRF_MONITOR; </pre> <br /> 자... 그럼 이 중에서 "Historical Debugging"과 같은 기능을 구현하려면 어떻게 해야 할까요? 그렇죠... ^^ "COR_PRF_MONITOR_JIT_COMPILATION" 플래그를 설정해야 합니다. 방법도 간단합니다. ICorProfilerCallback::Initialize 단계에서 ICorProfilerInfo::SetEventMask(COR_PRF_MONITOR_JIT_COMPILATION);으로 호출하면 이후로 ICorProfilerCallback::JITCompilationStarted 콜백이 불려지게 됩니다. 가장 중요한 기능인 IL 코드 변경은 바로 이 콜백 함수가 호출되는 단계에서 처리해주면 됩니다.<br /> <br /> 그런데, 실제로 해보면 결과가 원하는 대로 나오지 않는 것을 볼 수 있습니다.<br /> <br /> 예를 들어, "Windows Forms 빈 응용 프로그램"을 대상으로 아래와 같이 JITCompilationStarted 메서드를 구현해 놓으면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > HRESULT CNativeBridge::JITCompilationStarted(FunctionID functionId, BOOL fIsSafeToBlock) { IMetaDataImport * pIMetaDataImport = 0; HRESULT hr = S_OK; mdToken funcToken = 0; hr = m_pICorProfilerInfo->GetTokenAndMetaDataFromFunction (functionId, IID_IMetaDataImport, (LPUNKNOWN *) &pIMetaDataImport, &funcToken); ... [중간 생략] ... ... buf에는 JIT 컴파일될 함수의 이름 ... <b style='COLOR: blue'>LogEntry( L"\r\JITCompilationStarted - %s\r\n", buf ); </b> pIMetaDataImport->Release(); return S_OK; }</pre> <br /> 이때 출력되는 메서드 이름은 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > JITCompilationStarted - TestWinForm.Program.Main JITCompilationStarted - TestWinForm.Form1..ctor JITCompilationStarted - TestWinForm.Form1.InitializeComponent JITCompilationStarted - TestWinForm.Form1.Dispose </pre> <br /> 척 봐도... 뭔가 많이 모자랍니다. 가만 보니, 우리가 만든 Windows Forms 응용 프로그램에만 구현된 메서드에 대해서 콜백 함수가 불려졌다는 것을 알 수 있는데요. 이렇다 보니, 프로파일링을 걸어놨지만 속도는 거의 차이가 나지 않을 정도로 빠릅니다. 만약 .NET Framework 레벨의 메서드가 아닌 사용자가 만든 코드 수준에 대해서만 IL 재작성을 할 필요가 있다면 프로파일링 API를 붙이는 정도는 부담되지 않는 선택입니다. (한 예로, 사용자 코드에 대해서만 Call-graph를 작성한다든지!)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 하지만, Historical Debugging은 위와 같이 개발자가 만든 소스 코드만 JIT 컴파일링 되는 것을 잡아내는 것만으로는 구현이 안됩니다. .NET Framework 어셈블리까지도 IL 코드를 임의로 조작할 수 있어야 합니다.<br /> <br /> 생각해 보면, 여기에 약간의 어려움이 예상됩니다. 왜냐하면, .NET 프레임워크에서 제공되는 어셈블리들은 설치시에 NGen에 의해 미리 JIT 컴파일링되어 있기 때문에 IL 코드를 끼워넣을 수 있는 여지가 없기 때문입니다.<br /> <br /> 다행히, 이 부분에 대해서는 마이크로소프트 측에서 해법을 제공해 주고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > Creating an IL-rewriting profiler ; <a target='_tab' href='https://learn.microsoft.com/en-us/archive/blogs/davbr/creating-an-il-rewriting-profiler'>https://learn.microsoft.com/en-us/archive/blogs/davbr/creating-an-il-rewriting-profiler</a> </pre> <br /> 재미있지요. ^^ 우선 COR_PRF_USE_PROFILE_IMAGES 이벤트를 ICorProfilerInfo::SetEventMask에 추가합니다. 이걸 추가하게 되면, CLR은 NGen된 모듈을 로드하는 것을 거부하고 같은 버전의 IL 코드가 담긴 어셈블리를 로드해서 무조건 재컴파일하게 됩니다.<br /> <br /> 그래서, COR_PRF_USE_PROFILE_IMAGES를 적용하고 Windows Forms 빈 응용 프로그램을 실행하게 되면 다음과 같은 JIT 컴파일링 순서를 얻을 수 있습니다. (한마디로, 모든 메서드의 JIT 컴파일링 기록이 출력됩니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > JITCompilationStarted - System.AppDomain.SetupDomain JITCompilationStarted - System.AppDomainSetup.get_Value JITCompilationStarted - System.AppDomainSetup.set_DisallowBindingRedirects JITCompilationStarted - System.AppDomain.SetupFusionStore JITCompilationStarted - System.AppDomainSetup.get_DeveloperPath JITCompilationStarted - System.AppDomainSetup.VerifyDirList JITCompilationStarted - System.AppDomainSetup.set_DeveloperPath JITCompilationStarted - System.AppDomainSetup.SetupFusionContext JITCompilationStarted - System.String..cctor JITCompilationStarted - System.Text.StringBuilder..ctor JITCompilationStarted - System.String.GetStringForStringBuilder JITCompilationStarted - System.Text.StringBuilder.Append JITCompilationStarted - System.Text.StringBuilder.GetNewString ... 너무 많아서 생략 ... JITCompilationStarted - System.Windows.Forms.Internal.DeviceContext.get_BackgroundMode JITCompilationStarted - System.Windows.Forms.Internal.DeviceContext.SetBackgroundMode JITCompilationStarted - System.Windows.Forms.Internal.WindowsGraphics.AdjustForVerticalAlignment JITCompilationStarted - System.Drawing.Rectangle.get_Left JITCompilationStarted - System.Drawing.Rectangle.get_Top JITCompilationStarted - System.Windows.Forms.ButtonInternal.ButtonBaseAdapter.DrawFocus JITCompilationStarted - System.Windows.Forms.Control.get_ShowFocusCues JITCompilationStarted - System.Drawing.BufferedGraphics.Render JITCompilationStarted - System.Drawing.BufferedGraphics.RenderInternal JITCompilationStarted - System.Drawing.BufferedGraphics.Dispose JITCompilationStarted - System.Drawing.BufferedGraphics.Dispose </pre> <br /> 그런데, 여기서 한 가지 선택을 할 여지가 하나 있습니다. 만약 "모든 메서드"에 대해서 IL 코드를 끼워넣어야 한다면 굳이 COR_PRF_USE_PROFILE_IMAGES | COR_PRF_MONITOR_JIT_COMPILATION 조합을 사용하는 것은 너무 번거롭습니다. 차라리 COR_PRF_MONITOR_ENTERLEAVE를 사용하면 모든 메서드의 호출에 대해서 시작과 끝 지점에서 콜백을 받을 수 있는 메서드를 등록할 수 있기 때문입니다.<br /> <br /> 제가 잠시 테스트를 하고 나서 결론을 내려 본다면,<br /> COR_PRF_MONITOR_ENTERLEAVE에 실행될 코드를 지정하는 것은 너무 많은 오버헤드를 수반하기 때문에, 원하는 메서드가 호출된 경우에만 IL 코드를 삽입하는 COR_PRF_USE_PROFILE_IMAGES | COR_PRF_MONITOR_JIT_COMPILATION 조합이 더 현실적이라고 판단됩니다.<br /> <br /> 오늘은 여기까지만! ^^<br /> <br /><br /><hr /><span style='color: Maroon'>[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3135
(왼쪽의 숫자를 입력해야 합니다.)