C#动态委托首次调用性能劣化及预调用无效问题问询
首先,咱们先明确你看到的首次调用慢、后续快的核心原因:JIT(即时编译)编译。
一、首次测试时被缓存的内容
当你第一次调用objCreators[...].Invoke(...)或propSetters[...].Invoke(...)时,CLR(公共语言运行时)需要把这些Lambda表达式对应的IL(中间语言)编译成本地机器码——这个过程是有开销的。编译完成后,本地机器码会被缓存到当前应用域中,后续再调用同一个委托实例时,就直接执行缓存的机器码,不需要再编译,所以速度会大幅提升。
你提到即使让Lambda依赖c值,首次调用后性能还是能提升,这完全符合JIT的特性:JIT编译的是方法的结构逻辑,而不是具体的参数值。只要Lambda对应的匿名方法的IL结构不变,不管传入什么参数,编译后的机器码都是通用的,所以第一次编译后,后续所有调用都能复用缓存。
二、为什么预调用没复现缓存效果?
你预调用没起作用,大概率是因为预调用没有覆盖到实际测试中用到的所有委托实例。结合你的代码来看:
- 实际测试循环中,你是遍历
inputSelections里的每个selection,调用objCreators[selection.MarketID]和propSetters[selection.SelectionID]对应的委托。 - 如果你的预调用只是随便选几个虚拟参数调用,没有遍历
inputSelections中所有出现过的MarketID和SelectionID,甚至没有遍历objCreators和propSetters的全部Key,那么那些没被预调用的委托在第一次测试循环时,还是会触发JIT编译,导致首次测试耗时依然很高。
举个例子:假设objCreators里有10个不同的MarketID对应的Func,你预调用只测了2个,剩下8个在第一次测试循环时才第一次被调用,它们的JIT编译开销还是会加到首次测试的耗时里,所以你看不到预调用的效果。
三、验证优化的方法
要让预调用真正起作用,你需要确保所有在测试中会用到的委托都被触发一次JIT编译:
// 预调用所有objCreators中的委托 foreach (var kvp in objCreators) { // 传入任意符合签名的参数,比如0 var dummyObj = kvp.Value.Invoke(0); // 同时预调用所有propSetters中的委托 foreach (var setterKvp in propSetters) { setterKvp.Value.Invoke(dummyObj); } }
这样就能确保所有委托对应的匿名方法都被JIT编译过,再跑测试循环时,首次调用的耗时应该会和后续测试接近。
另外补充一点:如果你的objCreators中的Lambda是创建不同类型的对象(比如new SomeObjectType()、new AnotherObjectType()),那么每个不同类型的构造方法调用也可能触发各自的JIT编译,预调用时也要确保覆盖到所有这些类型的实例化操作。
内容的提问来源于stack exchange,提问作者Kobek

