Как доказать, что .NET CLR JIT компилирует каждый метод только один раз за запуск?

There старый вопрос, спрашивающий, собирается ли С# JIT каждый раз, и ответ знаменитого Jon Skeet: "нет, он скомпилирован только один раз за приложение" как долго поскольку мы говорим о настольных приложениях, которые не являются NGENed.

Я хочу знать, является ли эта информация с 2009 года по-прежнему истинным, и я хочу понять это путем эксперимента и отладки, потенциально, поставив точку останова на JITter и используя команды WinDbg для проверки объектов и методов.

Мои исследования пока

Я знаю, что макет памяти .NET рассматривает заголовок (по адресу А-4) и таблицу методов (по адресу А + 0) на объект до начала фактических данных (по адресу А + 4). Поэтому было бы возможно, что каждый объект имеет другую таблицу методов и, следовательно, может иметь разные JIT-методы.

Почему у меня возникают сомнения в правильности утверждения?

У нас был семинар для параллельного программирования, и одним из требований тренера было то, что методы JITted для каждого объекта на поток. Это явно не имело смысла для меня, и я смог написать пример приложения-счетчика.

К сожалению, появились следующие темы, для которых я также хочу написать демонстрацию:

  • новые платформы .NET
  • области приложений
  • безопасность доступа к коду

Связанный ответ был написан, когда был выпущен .NET 3.5. С тех пор он существенно не изменился, то есть он не получил обновлений для .NET 4.0, 4.6 и 4.6.

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

Что касается безопасности доступа к коду, я не уверен, что он рассматривается компилятором JIT на основе текущих разрешений или выполняется ли это во время выполнения. Если это будет сделано компилятором JIT, скомпилированный код будет отличаться в зависимости от набора разрешений.

Ответ 1

"Принцип проектирования" JIT состоит в том, чтобы компилировать метод (экземпляр метода для общих методов) один раз и повторно использовать один и тот же собственный код. Конечно, реализация чрезвычайно сложна, и я попытаюсь упростить ответ без ущерба для точности. Ответ одинаковый для всех версий среды выполнения с .NET 2.0 и до последнего .NET 4.6 (я не знаю о .NET 1.x, возможно, тот же).

Обратные вызовы профилировщика времени выполнения и события ETW плохо документированы. Оба они возникают при попытке компиляции JIT, но не обязательно. Есть три случая, когда это может произойти: 1- метод не отвечает определенным требованиям безопасности, 2 - проверка метода не удалась, и 3-я память не могла быть выделена для хранения исходящего кода. Таким образом, обратный вызов и событие запуска JIT могут переоценить количество случаев, когда метод фактически был скомпилирован. Аналогичным образом, обратный вызов завершения и события JIT являются неточными. Есть редко встречающиеся случаи, когда они могут недооценивать количество раз, когда тот же метод был успешно скомпилирован. На данный момент стоит упомянуть, что # из методов JITted счетчик производительности точно отражает количество раз, когда все IL-методы были скомпилированы в совокупности во всех областях приложения процесса.

Для ассемблерных ассемблеров методы компилируются в каждом приложении отдельно. Нет совместного доступа (хотя иногда это может быть технически возможно). Для групп, не поддерживающих appdomain, время выполнения пытается компилировать каждый метод один раз и совместно использовать собственный код со всеми доменами приложения.

Как доказать, что .NET CLR JIT компилирует каждый метод только один раз за работать?

Ну, в некоторых случаях (например, при использовании фона JIT и других чрезвычайно тонких случаях) метод может быть скомпилирован, даже не будучи выполненным. Таким образом, каждый метод скомпилирован один раз за прогон. Неверный.

Вы можете обратиться к исходному коду CoreCLR JIT для получения дополнительной информации (JIT - это тот же самый, который используется в .NET Framework 4.5+, но этот ответ относится к более старым версиям, поскольку механизм запуска JIT в основном одинаков). Исходный код является доказательством.

методы JITted для каждого объекта в потоке

Да, это не имеет никакого смысла. Объем компиляции - это приложения.