Компиляция C # JIT и .NET

86

Я немного запутался в деталях того, как работает JIT-компилятор. Я знаю, что C # компилируется до IL. При первом запуске выполняется JIT. Означает ли это, что он будет переведен в собственный код? Взаимодействует ли среда выполнения .NET (как виртуальная машина?) С кодом JIT? Я знаю, что это наивно, но я действительно запутался. Мне всегда казалось, что сборки не интерпретируются средой выполнения .NET, но я не понимаю деталей взаимодействия.

Стив
источник

Ответы:

83

Да, JIT-кодирование кода IL включает в себя перевод IL в машинные инструкции машинного кода.

Да, среда выполнения .NET взаимодействует с собственным машинным кодом JIT в том смысле, что среда выполнения владеет блоками памяти, занятыми собственным машинным кодом, вызовами среды выполнения в собственный машинный код и т. Д.

Вы правы, что среда выполнения .NET не интерпретирует код IL в ваших сборках.

Что происходит, когда выполнение достигает функции или блока кода (например, предложения else блока if), который еще не был JIT-скомпилирован в собственный машинный код, JIT'r вызывается для компиляции этого блока IL в собственный машинный код . Когда это будет сделано, выполнение программы вводит только что созданный машинный код для выполнения его программной логики. Если во время выполнения этого машинного кода выполнение достигает вызова функции для функции, которая еще не была скомпилирована в машинный код, JIT'r вызывается для компиляции этой функции «точно вовремя». И так далее.

JIT'r не обязательно компилирует сразу всю логику тела функции в машинный код. Если функция имеет операторы if, блоки операторов предложений if или else не могут быть JIT-скомпилированы до тех пор, пока выполнение не пройдет через этот блок. Неисполненные пути кода остаются в форме IL до тех пор, пока не будут выполнены.

Скомпилированный машинный код хранится в памяти, чтобы его можно было использовать снова при следующем выполнении этого раздела кода. Во второй раз, когда вы вызываете функцию, она будет работать быстрее, чем в первый раз, потому что во второй раз шаг JIT не требуется.

В настольной .NET машинный код хранится в памяти в течение всего времени существования домена приложения. В .NET CF собственный машинный код может быть отброшен, если приложению не хватает памяти. Он будет снова скомпилирован JIT из исходного кода IL, когда в следующий раз выполнение будет проходить через этот код.

Dthorpe
источник
14
Незначительное педантичное исправление - «JIT'r не обязательно компилирует всю логику тела функции» - В теоретической реализации это может иметь место, но в существующих реализациях среды выполнения .NET JIT-компилятор будет компилировать все методы сразу.
Пол Александр
18
Что касается того, почему это сделано, то это в первую очередь потому, что компилятор, не использующий jit, не может предполагать, что определенные оптимизации доступны на любой данной целевой платформе. Единственные варианты - либо скомпилировать с наименьшим общим знаменателем, либо, что чаще, скомпилировать несколько версий, каждая из которых нацелена на свою платформу. JIT устраняет этот недостаток, поскольку окончательный перевод в машинный код выполняется на целевой машине, где компилятор знает, какие оптимизации доступны.
Крис Шейн
1
Крис, даже подумал, что JIT знает, какие оптимизации доступны, у него нет времени применить какие-либо существенные оптимизации. Наверное, NGEN более мощный.
Григорий
2
@Grigory Да, у NGEN есть такая роскошь времени, которую нет у JIT-компилятора, но JIT может принять множество решений по кодогенерации, чтобы улучшить производительность скомпилированного кода, на выяснение которых не требуется много времени. Например, JIT-компилятор может выбирать наборы инструкций, которые наилучшим образом соответствуют доступному оборудованию, найденному во время выполнения, без добавления значительного времени на этап JIT-компиляции. Я не думаю, что .NET JITter делает это каким-либо существенным образом, но это возможно.
dthorpe
24

Код «компилируется» на промежуточном языке Microsoft, который аналогичен формату сборки.

Когда вы дважды щелкаете исполняемый файл, загружается Windows, mscoree.dllкоторая затем настраивает среду CLR и запускает код вашей программы. Компилятор JIT начинает чтение кода MSIL в вашей программе и динамически компилирует код в инструкции x86, которые может выполнять ЦП.

пользователь541686
источник
1

.NET использует промежуточный язык под названием MSIL, иногда сокращенно IL. Компилятор читает ваш исходный код и выдает MSIL. Когда вы запускаете программу, компилятор .NET Just In Time (JIT) считывает ваш код MSIL и создает исполняемое приложение в памяти. Вы этого не увидите, но лучше знать, что происходит за кулисами.

Гаган
источник
1

Я опишу компиляцию кода IL в инструкции собственного процессора на примере ниже.

public class Example 
{
    static void Main() 
    {
        Console.WriteLine("Hey IL!!!");
    }
}

В первую очередь CLR знает все подробности о типе и о том, какой метод вызывается из этого типа, это связано с метаданными.

Когда CLR начинает выполнять IL в инструкции собственного процессора, в это время CLR выделяет внутренние структуры данных для каждого типа, на который ссылается код Main.

В нашем случае у нас есть только одна консоль типа, поэтому CLR будет выделять одну внутреннюю структуру данных через эту внутреннюю структуру, мы будем управлять доступом к ссылочным типам

внутри этой структуры данных CLR содержит записи обо всех методах, определенных этим типом. Каждая запись содержит адрес, по которому можно найти реализацию метода.

При инициализации этой структуры CLR устанавливает каждую запись в недокументированную ФУНКЦИЮ, содержащуюся внутри самой среды CLR. И, как вы можете догадаться, эта ФУНКЦИЯ - это то, что мы называем JIT-компилятором.

В целом вы можете рассматривать JIT-компилятор как функцию CLR, которая компилирует IL в собственные инструкции ЦП. Я подробно покажу вам, как этот процесс будет происходить на нашем примере.

1. Когда Main делает свой первый вызов WriteLine, вызывается функция JITCompiler.

2.Функция JIT Compiler знает, какой метод вызывается и какой тип определяет этот метод.

3. Затем Jit Compiler ищет сборку, в которой определен этот тип, и получает код IL для метода, определенного этим типом, в нашем случае код IL метода WriteLine.

4. JIT-компилятор выделяет ДИНАМИЧЕСКИЙ блок памяти, после этого JIT проверяет и компилирует код IL в собственный код ЦП и сохраняет этот код ЦП в этом блоке памяти.

5. Затем JIT-компилятор возвращается к записи внутренней структуры данных и заменяет адрес (который в основном относится к реализации кода IL для WriteLine) адресом нового динамически создаваемого блока памяти, который содержит собственные инструкции ЦП WriteLine.

6. Наконец, функция JIT-компилятора переходит к коду в блоке памяти. Этот код является реализацией метода WriteLine.

7. После реализации WriteLine код возвращается к коду Mains, который продолжает выполнение как обычно.

So_oP
источник
0

.NET Framework использует среду CLR для создания MSIL (Microsoft Intermediate Language), также называемого IL. Компилятор считывает ваш исходный код и при сборке / компиляции проекта генерирует MSIL. Теперь, когда вы, наконец, запустите свой проект, в действие вступает .NET JIT ( Just-in-time Compiler ). JIT читает ваш код MSIL и создает собственный код (который является инструкциями x86), который может быть легко выполнен ЦП. JIT считывает все инструкции MSIL и выполняет их построчно.

Если вам интересно посмотреть, что происходит за кулисами, на это уже был дан ответ. Пожалуйста, следуйте - здесь

Дикшит Катурия
источник