Search code examples
c#.netclrjit

Running C# code fails only in release build (probably because of JIT compiler optimization)


The code below runs successfully in debug build, but is stalled after displaying "start" in release build. As long as I've looked into it, it seems to take a lot of time to compile the method in JIT compiler with optimization.

Why is it stalled and what is the work around?

I checked with visual studio 2017 community edition.

Edited:
It fails only when CPU=AnyCPU(remove prefer 32bit) or CPU=x64.

I just installed .Net Framework 4.7.1. (Target framework is .Net Framework 4.)
Even after installed .Net Framework 4.7.1, the problem still exists.

After setting useLegacyJit to the registry, it was solved. (when remove useLegacyJit, the problem came again.) It seems to be the bug with the RyuJIT.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args) {
            Console.WriteLine("start");
            Calculation calc = new Calculation();
            calc.Run();
            Console.WriteLine("end");
        }
    }

    class Calculation
    {
        double[] V0 = new double[1];
        double[] V1 = new double[1];
        double[] V2 = new double[1];
        double[] V3 = new double[1];
        double[] V4 = new double[1];
        double[] V5 = new double[1];
        double[] V6 = new double[1];
        double[] V7 = new double[1];
        double[] V8 = new double[1];
        double[] V9 = new double[1];
        double[] V10 = new double[1];
        double[] V11 = new double[1];
        double[] V12 = new double[1];
        double[] V13 = new double[1];
        double[] V14 = new double[1];
        double[] V15 = new double[1];
        double[] V16 = new double[1];
        double[] V17 = new double[1];
        double[] V18 = new double[1];
        double[] V19 = new double[1];
        double[] V20 = new double[1];
        double[] V21 = new double[1];
        double[] V22 = new double[1];
        double[] V23 = new double[1];
        double[] V24 = new double[1];
        double[] V25 = new double[1];
        double[] V26 = new double[1];
        double[] V27 = new double[1];
        double[] V28 = new double[1];
        double[] V29 = new double[1];
        double[] V30 = new double[1];
        double[] V31 = new double[1];
        double[] V32 = new double[1];
        double[] V33 = new double[1];
        double[] V34 = new double[1];
        double[] V35 = new double[1];
        double[] V36 = new double[1];
        double[] V37 = new double[1];
        double[] V38 = new double[1];
        double[] V39 = new double[1];
        double[] V40 = new double[1];
        double[] V41 = new double[1];
        double[] V42 = new double[1];
        double[] V43 = new double[1];
        double[] V44 = new double[1];
        double[] V45 = new double[1];
        double[] V46 = new double[1];
        double[] V47 = new double[1];
        double[] V48 = new double[1];
        double[] V49 = new double[1];

        public void Run()
        {
        for (int x = 0; x < 1; x++){V0[x] = 0.0;}
        for (int x = 0; x < 1; x++){V1[x] = 0.0;}
        for (int x = 0; x < 1; x++){V2[x] = 0.0;}
        for (int x = 0; x < 1; x++){V3[x] = 0.0;}
        for (int x = 0; x < 1; x++){V4[x] = 0.0;}
        for (int x = 0; x < 1; x++){V5[x] = 0.0;}
        for (int x = 0; x < 1; x++){V6[x] = 0.0;}
        for (int x = 0; x < 1; x++){V7[x] = 0.0;}
        for (int x = 0; x < 1; x++){V8[x] = 0.0;}
        for (int x = 0; x < 1; x++){V9[x] = 0.0;}
        for (int x = 0; x < 1; x++){V10[x] = 0.0;}
        for (int x = 0; x < 1; x++){V11[x] = 0.0;}
        for (int x = 0; x < 1; x++){V12[x] = 0.0;}
        for (int x = 0; x < 1; x++){V13[x] = 0.0;}
        for (int x = 0; x < 1; x++){V14[x] = 0.0;}
        for (int x = 0; x < 1; x++){V15[x] = 0.0;}
        for (int x = 0; x < 1; x++){V16[x] = 0.0;}
        for (int x = 0; x < 1; x++){V17[x] = 0.0;}
        for (int x = 0; x < 1; x++){V18[x] = 0.0;}
        for (int x = 0; x < 1; x++){V19[x] = 0.0;}
        for (int x = 0; x < 1; x++){V20[x] = 0.0;}
        for (int x = 0; x < 1; x++){V21[x] = 0.0;}
        for (int x = 0; x < 1; x++){V22[x] = 0.0;}
        for (int x = 0; x < 1; x++){V23[x] = 0.0;}
        for (int x = 0; x < 1; x++){V24[x] = 0.0;}
        for (int x = 0; x < 1; x++){V25[x] = 0.0;}
        for (int x = 0; x < 1; x++){V26[x] = 0.0;}
        for (int x = 0; x < 1; x++){V27[x] = 0.0;}
        for (int x = 0; x < 1; x++){V28[x] = 0.0;}
        for (int x = 0; x < 1; x++){V29[x] = 0.0;}
        for (int x = 0; x < 1; x++){V30[x] = 0.0;}
        for (int x = 0; x < 1; x++){V31[x] = 0.0;}
        for (int x = 0; x < 1; x++){V32[x] = 0.0;}
        for (int x = 0; x < 1; x++){V33[x] = 0.0;}
        for (int x = 0; x < 1; x++){V34[x] = 0.0;}
        for (int x = 0; x < 1; x++){V35[x] = 0.0;}
        for (int x = 0; x < 1; x++){V36[x] = 0.0;}
        for (int x = 0; x < 1; x++){V37[x] = 0.0;}
        for (int x = 0; x < 1; x++){V38[x] = 0.0;}
        for (int x = 0; x < 1; x++){V39[x] = 0.0;}
        for (int x = 0; x < 1; x++){V40[x] = 0.0;}
        for (int x = 0; x < 1; x++){V41[x] = 0.0;}
        for (int x = 0; x < 1; x++){V42[x] = 0.0;}
        for (int x = 0; x < 1; x++){V43[x] = 0.0;}
        for (int x = 0; x < 1; x++){V44[x] = 0.0;}
        for (int x = 0; x < 1; x++){V45[x] = 0.0;}
        for (int x = 0; x < 1; x++){V46[x] = 0.0;}
        for (int x = 0; x < 1; x++){V47[x] = 0.0;}
        for (int x = 0; x < 1; x++){V48[x] = 0.0;}
        for (int x = 0; x < 1; x++){V49[x] = 0.0;}
        }

    }
}

Solution

  • This indeed seems to be an issue with RyuJIT, which can be verified by temporarily disabling it:

    Set-ItemProperty -Path HKLM:\Software\Microsoft\.NETFramework -Name useLegacyJit -Type DWord -Value 1

    Tracking the actual issue down is a bit more involved, however, if we build coreclr v2.0.5 (latest stable), and run it with /v verbose flag and with COMPlus_JitDump=* Environment variable set, after some time it starts printing out a lot of logs like this one:

      AX2: $f5b != $1c0 ==> select([$277]store($dae, $1c0, $276), $f5b) ==> select($dae, $f5b).
      AX2: $f5b != $1c0 ==> select([$277]store($dae, $1c0, $276), $f5b) ==> select($dae, $f5b).
      AX2: $f5b != $1c0 ==> select([$279]store($dbd, $1c0, $278), $f5b) ==> select($dbd, $f5b).
      AX2: $f5b != $1c0 ==> select([$277]store($dae, $1c0, $276), $f5b) ==> select($dae, $f5b).
      AX2: $f5b != $1c0 ==> select([$277]store($dae, $1c0, $276), $f5b) ==> select($dae, $f5b).
      AX2: $f5b != $1c0 ==> select([$279]store($dbd, $1c0, $278), $f5b) ==> select($dbd, $f5b).
      AX2: $f5b != $1c0 ==> select([$27b]store($e0f, $1c0, $27a), $f5b) ==> select($e0f, $f5b).
      AX2: $f5b != $1c0 ==> select([$27d]store($e21, $1c0, $27c), $f5b) ==> select($e21, $f5b).
      AX2: $f5b != $1c0 ==> select([$27f]store($e32, $1c0, $27e), $f5b) ==> select($e32, $f5b).
    

    The method printing the log can be found at https://github.com/dotnet/coreclr/blob/v2.0.5/src/jit/valuenum.cpp#L1377

    The issue is most likely caused by budget variable overflowing or otherwise not reducing for some reason. Unfortunately, haven't had much time to investigate it further, but some changes to that method have been made here, and when I tried running the sample with master build of Core CLR the probleem seems to have been solved.

    As per Roadmap, it's due for release in Q1 2018. I'm not sure when is this going to be included in the actual .NET Framework release though.

    As a workaround, perhaps you can try refactoring the loops into their own methods:

    public void RunLoop(double[] arr)
    {
        for (int x = 0; x < 1; x++)
        {
            arr[x] = 0.0;
            // etc...
        }
    }
    

    Which seems to solve the issue as well (if it makes sense to do that in your actual code).