Когда метод может быть встроен в CLR?

Я наблюдал много «интроспективного» кода в приложениях, которые часто неявно полагаются на свои методы, которые не встроены для их правильности. Такие методы обычно включают вызовы:

  • MethodBase.GetCurrentMethod
  • Assembly.GetCallingAssembly
  • Assembly.GetExecutingAssembly

Теперь я нахожу информацию, касающуюся этих методов, очень запутанной. Я слышал, что во время выполнения не будет встроен метод, вызывающий GetCurrentMethod, но я не могу найти никакой документации на этот счет. Я несколько раз видел сообщения в StackOverflow, такие как этот, что указывает на то, что среда CLR не выполняет встроенные вызовы кросс-сборки, а GetCallingAssembly документация категорически указывает на обратное.

Есть также очень критикуемый [MethodImpl(MethodImplOptions.NoInlining)], но я не уверен, считает ли CLR это «запросом» или «командой».

Обратите внимание, что я спрашиваю о встраивании правомочности с точки зрения контракта, а не о том, когда текущие реализации JITter отказываются рассматривать методы из-за трудностей реализации, или о том, когда JITter в конце концов, после оценки компромиссов выбирает встраивание подходящего метода. Я прочитал это и this, но они, похоже, больше сосредоточены на последних двух баллов (иногда упоминаются MethodImpOptions.NoInlining и «экзотические инструкции IL», но они, похоже, представлены как эвристика, а не как обязательства).

Когда CLR разрешено встраиваться?


person Ani    schedule 11.01.2011    source источник
comment
Когда можно встраивать CLR? Я бы предположил, когда этого атрибута нет.   -  person CodesInChaos    schedule 11.01.2011
comment
@CodeInChaos: Значит, вы говорите, что Assembly.GetExecutingAssembly не работает, если мы не украсим метод [MethodImpl(MethodImpOptions.NoInlining)]?   -  person Ani    schedule 11.01.2011
comment
Вот хорошая ссылка (не отвечает на все ваши мысли мысли ...): blogs.msdn.com/b/vancem/archive/2008/08/19/   -  person Simon Mourier    schedule 21.01.2011
comment
Хм, звучит как ошибка спецификации: я понимаю техническую простоту этого в противном случае, но очевидно, что GetCallingAssembly и друзья никогда не должны были указываться как встроенные ... вздох - а в документах GetExecutingAssembly вообще не упоминается встраивание!   -  person Eamon Nerbonne    schedule 25.01.2011
comment
Не ответ на ваш вопрос, но начиная с Visual Studio 2012 и далее, получение имени метода / свойства вызывающего объекта, его местоположения файла и номера строки теперь может быть выполнено с помощью компилятора с помощью тега Информация о вызывающем абоненте, т. е. не зависит от встраивания JIT. Кроме того, это невероятно быстро, в отличие от проверки стека (создание фрейма и последующий).   -  person Evgeniy Berezovsky    schedule 17.07.2015


Ответы (5)


Это деталь реализации джиттера, джиттеры x86 и x64 имеют несколько разные правила. Это случайно задокументировано в блогах членов команды, которые работали над джиттером, но команды, безусловно, оставляют за собой право изменять правила. Похоже, вы их уже нашли.

Встраивание методов из других сборок, безусловно, поддерживается, многие классы .NET работали бы ужасно, если бы это было не так. Вы можете увидеть это в действии, если посмотрите на машинный код, сгенерированный для Console.WriteLine (), он часто становится встроенным, когда вы передаете простую строку. Чтобы убедиться в этом, вам нужно переключиться на сборку Release и изменить параметр отладчика. Инструменты + Параметры, Отладка, Общие, снимите флажок «Подавить оптимизацию JIT при загрузке модуля».

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

person Hans Passant    schedule 11.01.2011
comment
Из того, что вы сказали, могу ли я сделать вывод, что void M()``{Console.WriteLine(MethodBase.GetCurrentMethod().Name);} не гарантированно выводит M, но гарантированно выводит M после того, как я украсил его NoInlining? А как насчет Assembly.GetCallingAssembly и Assembly.GetExecutingAssembly? Я очень редко вижу, как код, вызывающий Assembly.GetExecutingAssembly, украшается атрибутом, хотя намерение this сборки присутствует. - person Ani; 11.01.2011
comment
Это правильно. Да, метод, содержащий подобный код, должен быть украшен атрибутом, если он общедоступный. Вероятно, он выживает благодаря размеру метода. - person Hans Passant; 11.01.2011

Несмотря на ответ Ханса Пассанта, здесь сначала пара подсказок по состоянию на 2004 год, а затем еще немного свежей информации. Они могут быть изменены, но они дают вам представление о том, что искать, если вы хотите сделать метод подходящим для встраивания:

JIT не будет встроенным:

  • Методы, отмеченные как MethodImplOptions.NoInlining
  • Методы размером более 32 байтов IL
  • Виртуальные методы
  • Методы, принимающие в качестве параметра тип с большим значением
  • Методы на классах MarshalByRef
  • Методы со сложными блок-графами
  • Методы, отвечающие другим, более экзотическим критериям

В частности, существует MethodImplOptions.AggressiveInlining, который должен снять ограничение в 32 байта (или что бы там ни было в наши дни и для вашей платформы).

.Net 3.5 добавил эвристику, которая помогает определить, есть ли Встраивать или не встраивать, что, вероятно, хорошо, хотя разработчику труднее предсказать решение о джиттере:

Цитата из статьи:

  1. Если встраивание делает код меньше, чем вызов, который он заменяет, это ВСЕГДА хорошо. Обратите внимание, что мы говорим о РАЗМЕРЕ ИСХОДНОГО кода, а не о размере кода IL (который может сильно отличаться).

  2. Чем больше выполняется конкретный сайт вызова, тем больше пользы он получит от встраивания. Таким образом, код в циклах заслуживает быть встроенным больше, чем код, который не находится в циклах.

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

Таким образом, эвристика, которую использует JIT-компилятор X86, предполагает наличие встроенного кандидата.

  1. Оцените размер сайта вызова, если метод не был встроен.

  2. Оцените размер сайта вызова, если он был встроен (это оценка, основанная на IL, мы используем простой конечный автомат (модель Маркова), созданный с использованием большого количества реальных данных для формирования этой логики оценки)

  3. Вычислите множитель. По умолчанию это 1

  4. Увеличьте множитель, если код находится в цикле (текущая эвристика увеличивает его до 5 в цикле)

  5. Увеличьте множитель, если похоже, что оптимизация структуры сработает.

  6. Если InlineSize ‹= NonInlineSize * Multiplier, сделайте встраивание.

person Evgeniy Berezovsky    schedule 17.07.2015

Хотя ответ Ганса является правильным, есть одно упущение, не обязательно касающееся того, когда метод подходит для встраивания, но когда метод не.

Абстрактные и виртуальные методы не подходят для встраивания в CLR.

Важно отметить, что это сокращает условия, при которых метод может быть встроенным.

person casperOne    schedule 26.01.2011
comment
Я не согласен; это деталь реализации, которая в настоящее время выполняется. [Мы могли бы потенциально добиться большего здесь (например, если 99% вызовов попадают в одну и ту же цель, вы можете сгенерировать код, который проверяет таблицу методов объекта, на котором будет выполняться виртуальный вызов, если это не так в случае 99% вы выполняете вызов, иначе вы просто выполняете встроенный код)]. blogs.msdn.com/b/davidnotario/archive/ 2004/11/01 / 250398.aspx - person Ani; 26.01.2011
comment
Случайно нашел это - и обнаружил, что ссылка на мой вопрос. :-) - person Frank V; 10.11.2014

Дополнительная информация о встраивании MethodBase.GetCurrentMethod в этот поток http://prdlxvm0001.codify.net/pipermail/ozdotnet/2011-March/009085.html

Сильно перефразируя, в нем говорится, что RefCrawlMark НЕ останавливает встраивание вызывающего метода. Однако у RequireSecObject есть побочный эффект, заключающийся в остановке встраивания вызывающего объекта.

Кроме того, методы Assembly.GetCallingAssembly и Assembly.GetExecutingAssembly НЕ имеют этого атрибута.

person stew    schedule 09.06.2012

В 2003 году в MSDN была опубликована статья под названием Написание высокопроизводительных управляемых приложений, который довольно четко охватывает несколько критериев:

  • Методы, длина которых превышает 32 байта IL, не будут встроены.
  • Виртуальные функции не встроены.
  • Методы со сложным управлением потоком не будут встроены. Сложное управление потоком - это любое управление потоком, кроме if / then / else; в этом случае переключитесь или пока.
  • Методы, содержащие блоки обработки исключений, не встраиваются, хотя методы, генерирующие исключения, по-прежнему являются кандидатами на встраивание.
  • Если какой-либо из формальных аргументов метода является структурами, метод не будет встроен.

Статья в блоге Саши Гольдштейна в 2012 году на агрессивное встраивание в CLR дает во многом тот же совет.

person cdiggins    schedule 30.03.2019