Ответ 1
.NET JIT - плохой компилятор, это правда. К счастью, новый JIT (RyuJIT) и NGEN, который, кажется, основан на компиляторе VC, находятся в работе (я считаю, что это что использует Компилятор облаков Windows Phone.
Хотя это очень простой компилятор, он делает встроенные небольшие функции и в определенной степени удаляет свободные контуры побочных эффектов. Это не очень хорошо, но это происходит.
Прежде чем перейти к подробным выводам, обратите внимание, что x86 и x64 JIT - это разные кодовые базы, работают по-разному и имеют разные ошибки.
Тест 1:
Вы запускали программу в режиме Release в 32-битном режиме. Я могу воспроизвести ваши результаты на .NET 4.5 с 32-разрядным режимом. Да, это неловко.
В 64-битном режиме, однако, Rem
в первом примере встроен и внутренняя из двух вложенных циклов удаляется:
Я отметил три инструкции цикла. Внешний контур все еще существует. Я не думаю, что это когда-либо имеет значение на практике, потому что у вас редко есть две вложенные мертвые петли.
Обратите внимание, что цикл был развернут 4 раза, затем развернутые итерации были свернуты в одну итерацию (разворачивание создало i += 1; i+= 1; i+= 1; i+= 1;
и было свернуто до i += 4;
). Разумеется, весь цикл можно было бы оптимизировать, но JIT действительно выполнял наиболее важные на практике вещи: разворачивание циклов и упрощение кода.
Я также добавил следующее в Main
, чтобы упростить отладку:
Console.WriteLine(IntPtr.Size); //verify bitness
Debugger.Break(); //attach debugger
Тест 2:
Я не могу полностью воспроизвести ваши результаты в 32-битном или 64-битном режиме. Во всех случаях Test2
встроен в Test1
, что делает его очень простой функцией:
Main
вызывает Test1
в цикле, потому что Test1
слишком велик для встроенного (потому что не упрощенный размер подсчитывается, потому что методы JIT'ы изолированы).
Если у вас есть только один вызов Test2
в Test1
, то обе функции достаточно малы, чтобы быть встроенными. Это позволяет JIT для Main
обнаруживать, что в этом коде ничего не делается вообще.
Окончательный ответ: Надеюсь, я смогу пролить свет на то, что происходит. В этом процессе я обнаружил некоторые важные оптимизации. JIT просто не очень тщательна и полна. Если бы одна и та же оптимизация была просто выполнена во втором идентификационном проходе, здесь было бы намного упрощено. Но большинству программ нужен только один проход через все упрощения. Я согласен с выбором команды JIT, сделанной здесь.
Так почему же JIT так плохо? Одна часть состоит в том, что он должен быть быстрым, потому что JITing чувствителен к задержкам. Другая часть состоит в том, что это всего лишь примитивный JIT и требует больших инвестиций.