2

我有简单的 for 循环和使用 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 代码时,最好将类的实例保存在评估堆栈或局部变量中?

例如,我得到实例,遍历字段,为每个字段做任何事情然后返回。在读取下一个字段之前,我刚刚将实例保存在堆栈上并调用了 Emit(OpCodes.Dup)。但这似乎是错误的(至少对于上述情况)。

任何关于生成(高效/格式良好)IL 代码的文章/博客文章都值得赞赏。

4

2 回答 2

2

一般来说,使用局部变量通常会产生更易于调试的可读代码,鉴于 IL 已经不是大多数开发人员习惯阅读的东西,这很重要。JIT 甚至有可能消除这样做可能带来的任何性能损失。

从我在 ILSpy 中看到的情况来看,csc 也更喜欢本地人,尽管我不得不承认,当我查看 IL 而不是反编译为 C# 时,它主要是调试代码。由于 JIT 的编写可能期望它主要运行在 Microsoft 编译器的输出上,因此如果它不能识别与其编译器发出的内容不匹配的循环构造,那就不足为奇了。额外的堆栈条目阻碍了 JIT 认识到它可以消除边界检查的能力,这是非常合理的。

于 2013-03-14T19:07:50.390 回答
0

在您的方法被 Jitted 之前,您是否在未附加调试器的情况下以发布模式运行?然后再贴上去?我知道执行此步骤似乎没有必要,但如果附加了调试器,调试器将发出不太理想的代码。

包括您认为错误所在的整个方法。我建议使用该方法发出一个程序集,以便您可以针对它运行 PEVerify。有时你会是编译但无效的代码。

unboxed T对于无效的代码(例如,错误堆栈,如仅与对象一起运行的通用代码上预期的堆栈) ,抖动可能会非常困难boxed T,尤其是无法验证,这不是正常模式。(例如,通过 C# 或 C++/CLI 永远不会发生的不安全代码)。除非您期待一个错误,否则您应该始终尝试在 peverify 中出现 0 个错误。(例如calli

于 2013-03-18T15:53:27.643 回答