如何让本地.NET运行时与生产环境转储采用相同JIT编译方式?
我有两份生产环境转储文件,其中System.ComponentModel.Composition.dll程序集中的System.ComponentModel.Composition.Hosting.CompositionLock.LockComposition方法的JIT编译代码如下:
0:146> !U /d 00007ffc93d72c10 Normal JIT generated code System.ComponentModel.Composition.Hosting.CompositionLock.LockComposition() Begin 00007ffc93d72c10, size 53 >>> 00007ffc`93d72c10 57 push rdi 00007ffc`93d72c11 56 push rsi 00007ffc`93d72c12 4883ec28 sub rsp,28h 00007ffc`93d72c16 488bf1 mov rsi,rcx 00007ffc`93d72c19 807e1400 cmp byte ptr [rsi+14h],0 00007ffc`93d72c1d 7427 je 00007ffc`93d72c46 00007ffc`93d72c1f 48b9487a2f9cfc7f0000 mov rcx,7FFC9C2F7A48h (MT: System.ComponentModel.Composition.Hosting.CompositionLock+CompositionLockHolder) 00007ffc`93d72c29 e8e2f8ef54 call clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffc`e8c72510) 00007ffc`93d72c2e 488bf8 mov rdi,rax 00007ffc`93d72c31 488bcf mov rcx,rdi 00007ffc`93d72c34 488bd6 mov rdx,rsi 00007ffc`93d72c37 e844e5ffff call 00007ffc`93d71180 (System.ComponentModel.Composition.Hosting.CompositionLock+CompositionLockHolder..ctor(System.ComponentModel.Composition.Hosting.CompositionLock), mdToken: 000000000600061e) 00007ffc`93d72c3c 488bc7 mov rax,rdi 00007ffc`93d72c3f 4883c428 add rsp,28h 00007ffc`93d72c43 5e pop rsi 00007ffc`93d72c44 5f pop rdi 00007ffc`93d72c45 c3 ret 00007ffc`93d72c46 b977050000 mov ecx,577h 00007ffc`93d72c4b ba61000000 mov edx,61h 00007ffc`93d72c50 e80b00f054 call clr!JIT_GetSharedGCStaticBase_InlineGetAppDomain (00007ffc`e8c72c60) 00007ffc`93d72c55 488b8038010000 mov rax,qword ptr [rax+138h] 00007ffc`93d72c5c 4883c428 add rsp,28h 00007ffc`93d72c60 5e pop rsi 00007ffc`93d72c61 5f pop rdi 00007ffc`93d72c62 c3 ret
除绝对内存地址不同外,两份转储的代码完全一致。
在本地运行的示例应用中检查同一方法时,得到的代码差异很大:
0:063> !U /d 00007ff93aa22e70 Normal JIT generated code System.ComponentModel.Composition.Hosting.CompositionLock.LockComposition() Begin 00007ff93aa22e70, size 7e >>> 00007ff9`3aa22e70 57 push rdi 00007ff9`3aa22e71 56 push rsi 00007ff9`3aa22e72 53 push rbx 00007ff9`3aa22e73 4883ec30 sub rsp,30h 00007ff9`3aa22e77 488bf1 mov rsi,rcx 00007ff9`3aa22e7a 400fb67e14 movzx edi,byte ptr [rsi+14h] 00007ff9`3aa22e7f 85ff test edi,edi 00007ff9`3aa22e81 744d je 00007ff9`3aa22ed0 00007ff9`3aa22e83 48b9f053b13af97f0000 mov rcx,7FF93AB153F0h (MT: System.ComponentModel.Composition.Hosting.CompositionLock+CompositionLockHolder) 00007ff9`3aa22e8d e81ed1a45e call clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ff9`9946ffb0) 00007ff9`3aa22e92 488bd8 mov rbx,rax 00007ff9`3aa22e95 488d4b08 lea rcx,[rbx+8] 00007ff9`3aa22e99 488bd6 mov rdx,rsi 00007ff9`3aa22e9c e8bfada45e call clr!JIT_WriteBarrier (00007ff9`9946dc60) 00007ff9`3aa22ea1 33c9 xor ecx,ecx 00007ff9`3aa22ea3 894b10 mov dword ptr [rbx+10h],ecx 00007ff9`3aa22ea6 85ff test edi,edi 00007ff9`3aa22ea8 741b je 00007ff9`3aa22ec5 00007ff9`3aa22eaa b96b000000 mov ecx,6Bh 00007ff9`3aa22eaf ba61000000 mov edx,61h 00007ff9`3aa22eb4 e847d8a45e call clr!JIT_GetSharedGCStaticBase_InlineGetAppDomain (00007ff9`99470700) 00007ff9`3aa22eb9 488b8830010000 mov rcx,qword ptr [rax+130h] 00007ff9`3aa22ec0 e88bd2a45e call clr!JIT_MonEnter (00007ff9`99470150) 00007ff9`3aa22ec5 488bc3 mov rax,rbx 00007ff9`3aa22ec8 4883c430 add rsp,30h 00007ff9`3aa22ecc 5b pop rbx 00007ff9`3aa22ecd 5e pop rsi 00007ff9`3aa22ece 5f pop rdi 00007ff9`3aa22ecf c3 ret 00007ff9`3aa22ed0 b96b000000 mov ecx,6Bh 00007ff9`3aa22ed5 ba61000000 mov edx,61h 00007ff9`3aa22eda e821d8a45e call clr!JIT_GetSharedGCStaticBase_InlineGetAppDomain (00007ff9`99470700) 00007ff9`3aa22edf 488b8038010000 mov rax,qword ptr [rax+138h] 00007ff9`3aa22ee6 4883c430 add rsp,30h 00007ff9`3aa22eea 5b pop rbx 00007ff9`3aa22eeb 5e pop rsi 00007ff9`3aa22eec 5f pop rdi 00007ff9`3aa22eed c3 ret
生产服务器上的System.ComponentModel.Composition.dll版本为4.8.3761.0,本地版本为4.8.9032.0——版本略有差异,但通过IL Spy查看,目标方法的IL代码未发生变化。
该方法的C#代码如下:
public IDisposable LockComposition() { if (_isThreadSafe) { return new CompositionLockHolder(this); } return _EmptyLockHolder; }
可见服务器端未内联CompositionLockHolder实例的创建逻辑,而本地代码则进行了内联。相关关联代码如下:
public void SatisfyImportsOnce(ComposablePart part) { ... using (_lock.LockComposition()) { ... } } ... public CompositionLockHolder(CompositionLock @lock) { _lock = @lock; _isDisposed = 0; _lock.EnterCompositionLock(); } ... private void EnterCompositionLock() { if (_isThreadSafe) { Monitor.Enter(_compositionLock); } }
请问能否配置本地.NET运行时,使其采用与生产环境相同的JIT编译方式?
可以通过以下几种方式配置本地.NET Framework运行时,使其JIT编译行为与生产环境对齐:
1. 匹配.NET Framework版本
生产环境使用的是.NET Framework 4.8.3761.0,本地则是4.8.9032.0,虽然IL代码一致,但不同版本的CLR JIT编译器可能存在优化策略差异。建议在本地安装与生产环境完全相同的.NET Framework更新版本,这是最直接确保JIT行为一致的方式。
2. 禁用JIT优化
生产环境的代码未进行内联优化,可通过以下方式禁用本地JIT的优化:
- 环境变量:启动应用前设置环境变量:
COMPLUS_JITOptimizer=0 COMPLUS_JITInline=0 - 应用配置文件:在应用的
app.config或web.config中添加配置:<configuration> <runtime> <disableJITOptimizer enabled="true" /> <disableJITInline enabled="true" /> </runtime> </configuration> - 注册表设置(全局生效,不推荐):
在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework下添加DWORD值JITOptimizer和JITInline,值设为0。
3. 匹配其他JIT相关配置
如果生产环境有自定义的JIT配置,还需要同步以下可能的设置:
- 检查生产环境是否设置了
COMPLUS_TieredCompilation环境变量,若生产环境禁用了分层编译,本地也需设置:COMPLUS_TieredCompilation=0 - 同步
COMPLUS_ReadyToRun等与预编译相关的配置,确保本地未启用ReadyToRun,与生产环境一致。
验证配置生效
修改配置后,可通过以下方式验证JIT编译行为是否与生产环境一致:
- 使用WinDbg附加到本地进程,执行
!U /d <方法地址>查看汇编代码,对比生产环境的转储结果。 - 检查应用运行时的性能特征或日志,确认优化行为已对齐。
内容的提问来源于stack exchange,提问作者mark

