我使用ILGenerator编写的数组访问循环很简单。当使用这个确切的代码创建方法时,我打开反汇编,没关系,没有数组边界检查。
但是当我第一次将其他类的实例放在评估堆栈上,然后运行for循环时,它会进行数组边界检查。我正在发布。
知道为什么吗?我已经阅读过有关数组绑定检查的博文:http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds-check-elimination-in-the-clr.aspx
// Uncomment this to enable bound checks, type of arg0 is some my class
//il.Emit(OpCodes.Ldarg_0);
var startLbl = il.DefineLabel();
var testLbl = il.DefineLabel();
var index = il.DeclareLocal(typeof(Int32));
var arr = il.DeclareLocal(typeof(Int32).MakeArrayType());
// arr = new int[4];
il.Emit(OpCodes.Ldc_I4_4);
il.Emit(OpCodes.Newarr, typeof(Int32));
il.Emit(OpCodes.Stloc, arr);
// Index = 0
il.Emit(OpCodes.Ldc_I4_0); // Push index
il.Emit(OpCodes.Stloc, index); // Pop index, store
il.Emit(OpCodes.Br_S, testLbl); // Go to test
// Begin for
il.MarkLabel(startLbl);
// Load array, index
il.Emit(OpCodes.Ldloc, arr);
il.Emit(OpCodes.Ldloc, index);
// Now on stack: array, index
// Load element
il.Emit(OpCodes.Ldelem_I4);
// Nothing here now, later some function call
il.Emit(OpCodes.Pop);
// Index++
il.Emit(OpCodes.Ldloc, index);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Stloc, index);
il.MarkLabel(testLbl);
// Load index, count, test for end
il.Emit(OpCodes.Ldloc, index);
il.Emit(OpCodes.Ldloc, arr);
il.Emit(OpCodes.Ldlen); // Push len
il.Emit(OpCodes.Conv_I4); // Push len
il.Emit(OpCodes.Blt_S, startLbl);
// End for
// Remove instance added on top
//il.Emit(OpCodes.Pop);
在生成IL代码时,最好将类的实例保留在评估堆栈或局部变量中?
E.g。我得到实例,通过字段,每个字段做任何事情而不是返回。我只是将实例保留在堆栈上并在读取下一个字段之前调用Emit(OpCodes.Dup)。但这似乎是错误的(至少在上面提到的情况下)。
有关生成(高效/格式良好)IL代码的任何文章/博客帖子都很受欢迎。
答案 0 :(得分:2)
通常使用locals通常会产生更易读的代码,因为IL已经不是大多数开发人员习惯阅读的东西了。 JIT甚至有可能消除可能存在的性能损失。
从我在ILSpy中看到的内容来看,csc也喜欢本地人,虽然我不得不承认当我看到IL而不是反编译为C#时,它主要是调试代码。由于JIT可能是因为期望它主要运行在微软编译器的输出上,所以如果它不能识别与编译器发出的不匹配的循环结构,那就不足为奇了。额外的堆栈条目阻碍了JIT识别它可以消除边界检查的能力,这是非常合理的。
答案 1 :(得分:0)
在您的方法被Jitted之前,您是否在调试器未连接的情况下以释放模式运行?然后附上? 我知道执行此步骤似乎不用了,但如果连接了调试器,调试器将发出不太理想的代码。
包括您认为错误所在的整个方法。我建议使用该方法发出一个程序集,以便您可以针对它运行PEVerify。有时您将编译代码,但无效。
对于无效的代码,抖动可能会非常困难(例如,对于仅在对象上运行的通用代码,在预期unboxed T
上的堆栈上的错误堆栈boxed T
。并且尤其是无法验证的不是正常的模式。 (例如,永远不会通过C#或C ++ / CLI发生的不安全代码)。你应该总是尝试在peverify中有0个错误,除非你期望一个。 (例如calli
)