JIT .Net编译器有bug吗?

22
以下代码的结果将因在后台启动还是没有启动调试器而不同。只有在开启优化时才会有差异。
这是结果:
-> 带优化: 1000 2008 3016 1001 2009 3007 ...
-> 不带优化(预期的结果) 1000 1008 1016 1001 1009 1017 ...
代码:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace OptimizerTest
{   
    public class Test
    {
        int dummy;

        public void TestFunction(int stepWidth)
        // stepWidth must be a parameter
        {
            for (int step = 0; step < stepWidth; step++)
            {
                dummy = step + 1000;
                // addition with constant ( same value as later !)
                for (int x = 0; x < 20; x += stepWidth)
                {
                    int index = x + 1000 + step;
                    // constant must be same as above and ?!?! 
                    // int index = x + step + 1000; works !!!!!
                    Console.Write("\n\r" + index);
                }
            }
        }

        [MethodImpl(MethodImplOptions.NoOptimization)]
        public void TestFunctionNoOptimization(int stepWidth)
        {
            for (int step = 0; step < stepWidth; step++)
            {
                dummy = step + 1000;
                for (int x = 0; x < 20; x += stepWidth)
                {
                    int index = x + 1000 + step;                        
                    Console.Write("\n\r" + index);
                }
            }
        }
    }

    class Program
    {
        /// <summary>
        /// Result differs from Start with F5 to Ctrl-F5
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            Test test = new Test();
            Console.Write("\n\r---------\n\roptimized result\n\r-------------" );
            test.TestFunction(8);
            Console.Write("\n\r---------\n\rnot optimized result\n\r-------------");
            test.TestFunctionNoOptimization(8);
            Console.Write("\n\r---------\n\rpress any key");
            Console.ReadKey();
        }
    }
}

错误的行为取决于内部循环的迭代次数(x<5时一切正常)。非常有趣的是,当我使用

时,错误不会发生。


The behavior of the error depends on the number of iterations of the inner loop (everything works fine when x<5). Interestingly, the error does not occur when I use.

   int index = x + step + 1000; 

而不是

   int index = x + 1000 + step; 

我正在使用Visual Studio 2010 SP1,并尝试了.NET Framework从2.0到4.0.3。 我一直看到相同的结果。

是否有人知道这个错误或者能够重现它?


ILSpy 给我返回了我输入的内容。看起来没问题。 - Jan
5
@UweKeim 我有不同的看法 :D - Rawling
2
@UweKeim:如果你想的话,可以指出Jan犯了什么错误。;-) - Chris
1
我在使用VS 2010 SP1进行x64构建时得到与您相同的结果,但在x86构建中正常工作。 - StevieB
1
我仔细检查了我的项目设置,发现平台目标设置为x64。使用x86是可以的,但是使用x64会失败。感谢测试。 - Jan
显示剩余5条评论
1个回答

20

是的,这绝对是一个抖动优化器的bug。其他Stack Overflow用户无法复现它的原因是只有x64抖动优化器似乎有这个bug。您必须将项目的平台目标设置为AnyCPU,在VS2012及以上版本中取消选中“首选32位”选项。

我没有仔细研究过根本原因,但它似乎在试图消除常见的step + 1000子表达式时出了问题。子表达式消除是标准的抖动优化之一。但它错误地将表达式代码合并到循环内部,而不是按原样将其保留在循环外部。例如,当您编写以下内容时,您会看到该bug消失:

  dummy = step + 999;

这个漏洞仍然存在于最新的.NET 4.5.1版本 (clrjit.dll, v4.0.30319.34003 在我的机器上), 还存在于v2 jit (mscorjit.dll, v2.0.50727.7905 在我的机器上)中。

这段代码有点过于抽象,所以很难推荐一个可靠的解决方法,但你已经发现了一种解决方案,所以可以继续在项目中使用它。总的来说,我建议你自己消除子表达式:

  int index = x + dummy;  

这应该向微软报告,您可以通过在connect.microsoft.com上发布错误报告来这样做。如果您没有时间,请让我知道,我会处理它。


这确实应该被报告。然而,我怀疑他们会修复它,因为显然这是第一次出现,并且他们正在开发一个新的JIT。有人尝试过使用ryuJit吗?(http://blogs.msdn.com/b/dotnet/archive/2013/11/18/ryujit-net-jit-compiler-ctp1-faq.aspx) - Kris Vandermotten
请通知微软。我理解得对吗,这个错误也存在于VS2012 x64中?tx - Jan
7
提交为反馈项812093 - Hans Passant
只需将虚拟变量赋值移至循环内部,并使用 index = x + dummy 进行逻辑复现。 - Hans Passant
是的,在RyuJIT中不会出现这种情况。如果我们只专注于让RyuJIT准备好,而不修复这个问题,那会有多糟糕呢?(说真的,在JIT64中修复问题很不愉快 :-)) - Kevin Frei
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接