Как это сделать the.NET JITs оптимизирует сгенерированный макет кода?


Еще в 2009 году я опубликовал этот ответ на вопрос об оптимизации для вложенных try/catch/finally блоки.

Думая об этом снова несколько лет спустя, кажется, что вопрос может быть распространен на этот другой поток управления, а не только try/catch/finally, но также if/else.

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

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

Я не думаю, что петли (for/foreach/while) являются ли они такими хорошими, если вы не ожидаете, что цикл имеет нулевые итерации чаще, чем некоторые, так как естественный порядок генерации кажется довольно оптимальный.

Некоторые вопросы:

  • какими способами доступные .NET JITs оптимизируются для генерируемого порядка команд?
  • насколько это может отличаться на практике от обычного кода? А как насчет идеально подходящих случаев?
  • Есть ли что-нибудь, что разработчик может сделать, чтобы повлиять на этот макет? А как насчет извращения с запретным goto?
  • имеет ли конкретный используемый JIT большое значение для макета?
  • является ли метод встраивания эвристическим вступать в игру и здесь?
  • в основном ничего интересного, связанного с этим аспектом JIT!

Некоторые начальные мысли:

Перемещение catch блоков вне линии-это легкая работа, поскольку они должны быть исключительным случаем по определению. Не уверен, что это произойдет.

Для некоторых циклов я подозреваю, что вы можете увеличить производительность нетривиально. Однако в целом я не думаю, что это будет иметь большое значение.

Я не знаю, как Джит решает проблему. порядок сгенерированного кода. В C на Linux у вас есть likely(cond) и еще unlikely(cond) который вы можете использовать, чтобы сообщить компилятору, какая ветвь является общим путем для оптимизации. Я не уверен, что все компиляторы уважают эти макросы.

Упорядочение команд отличается от задачи предсказания ветвей, в которой процессор угадывает (самостоятельно, afaik), какая ветвь будет взята для запуска конвейера (упрощенные шаги: декодирование, извлечение операндов, выполнение, запись) по инструкциям, перед выполнением шага определяется значение переменной условия.

Я не могу придумать никакого способа повлиять на этот порядок в языке C#. Возможно, вы можете немного манипулировать им с помощью gotoING к меткам явно, но является ли это переносимым, и есть ли какие-либо другие проблемы с ним?

Возможно, именно для этого и нужна профильная оптимизация. Есть ли это в экосистеме .NET, сейчас или в плане? Может быть, я пойду и почитаю оLLILC .

1 3

1 ответ:

Оптимизация, на которую вы ссылаетесь, называется оптимизацией компоновки кода, которая определяется следующим образом:

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

Теперь перейдем к вашим вопросам.

Каким образом доступные .NET JITs оптимизируются для генерируемых приказ об инструктаже?

"Порядок инструкций" - это действительно очень общий термин. Многие оптимизации влияют на порядок команд. Я предполагаю, что вы имеете в виду макет кода.

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

Насколько это может отличаться на практике от обычного кода? Что идеально подходит дела?

Оптимизация компоновки кода сама по себе может улучшить общую производительность, как правило, на -1% (отрицательный) до 4%, что достаточно, чтобы сделать авторов компиляторов счастливыми. Я хотел бы добавить, что это снижает потребление энергии косвенно, уменьшая пропуски кэша. Уменьшение коэффициента пропускания кэша команд обычно может достигать 35%.

Есть ли что-нибудь, что разработчик может сделать, чтобы повлиять на этот макет? Что о том, как калечить запретное Гото?

Да, есть множество способов. Я хотел бы упомянуть в целом рекомендуемый, который является mpgo.exe . Пожалуйста, не используйте Гото для этой цели. Это запрещено.

Имеет ли конкретный используемый JIT большое значение для макета?

Нет.

Входит ли здесь в игру и метод, лежащий в основе эвристики?

Инлайнинг действительно может улучшить компоновку кода по отношению к вызовам функций. Это один из самых важные оптимизации и все .NET JITs выполняют его.

Перемещение блоков catch из линии-это легкая работа, как и должно быть быть исключительным случаем по определению. Не уверен, что это произойдет.

Да, это может быть "легко", но какова потенциальная выгода? Блоки catch обычно имеют небольшой размер (содержат вызов функции, которая обрабатывает исключение). Обработка этого конкретного случая компоновки кода не кажется многообещающей. Если тебе действительно не все равно, используйте mpgo.exe .

Я не знаю, как JIT решает порядок сгенерированного кода. In C on Linux, вы, вероятно, (Конд) и вряд ли(Конд), которое можно использовать для сообщите компилятору, какая ветвь является общим путем для оптимизации.

Использование PGO намного предпочтительнее, чем использование likely(cond) и unlikely(cond) по двум причинам:

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

Упорядочение инструкций отличается от задачи ветвления предсказание...

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

Может быть, я пойду и почитаю о LLILC.

При использовании mpgo.exe является основным способом выполнения этой оптимизации, вы можете использовать LLILC также, так как LLVM поддерживает профильную оптимизацию. Но я не думаю, что тебе нужно заходить так далеко.