Оптимизация статических языков во время выполнения: JIT для C ++?

Кто-нибудь использует трюки JIT для повышения производительности статически компилируемых языков, таких как C ++, во время выполнения? Похоже, что анализ горячих точек и прогноз ветвления на основе наблюдений, сделанных во время выполнения, могут улучшить производительность любого кода, но, возможно, есть какая-то фундаментальная стратегическая причина, по которой такие наблюдения и реализация изменений во время выполнения возможны только на виртуальных машинах. Я отчетливо помню, как я слышал, как разработчики компилятора C ++ бормотали: «Вы можете сделать это и для программ, написанных на C ++», пока слушали энтузиасты динамического языка, рассказывающие о сборе статистики и изменении кода, но мои поиски в Интернете доказательств, подтверждающих эту память, не дали результатов.


person Thomas L Holaday    schedule 23.04.2009    source источник


Ответы (7)


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

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

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

person Whatever    schedule 23.04.2009

В последние годы я довольно часто занимался такой оптимизацией. Это было для API графического рендеринга, который я реализовал. Поскольку API определил несколько тысяч различных режимов рисования, функция общего назначения работала медленно.

В итоге я написал свой собственный маленький Jit-компилятор для предметно-ориентированного языка (очень близкого к asm, но с добавлением некоторых структур управления высокого уровня и локальных переменных).

Улучшение производительности, которое я получил, составило от 10 до 60 раз (в зависимости от сложности скомпилированного кода), поэтому дополнительная работа окупилась.

На ПК я бы не стал писать свой собственный jit-компилятор, а использовал для jit-компиляции либо LIBJIT, либо LLVM. В моем случае это было невозможно из-за того, что я работал над неосновным встроенным процессором, который не поддерживается LIBJIT / LLVM, поэтому мне пришлось изобрести свой собственный.

person Nils Pipenbrinck    schedule 23.04.2009
comment
++ Другой способ, которым я видел это, был грубым, но эффективным. Выделите блок стека и на лету сгенерируйте специализированную процедуру на машинном языке и вызовите ее. Я большой поклонник кодогенерации. - person Mike Dunlavey; 24.04.2009

Ответ более вероятен: никто не делал больше, чем PGO для C ++, потому что преимущества, вероятно, незаметны.

Позвольте мне уточнить: движки / среды выполнения JIT имеют как преимущества, так и недостатки с точки зрения разработчика: они имеют больше информации во время выполнения, но очень мало времени для анализа. Некоторые оптимизации действительно дороги, и вы вряд ли увидите, без огромного влияния на время запуска, такие как: развертывание цикла, автоматическая векторизация (которая в большинстве случаев также основана на развертывании цикла), выбор инструкций (для использования SSE4.1 для ЦП, использующий SSE4.1) в сочетании с планированием и переупорядочением инструкций (для использования лучших суперскалярных ЦП). Этот вид оптимизации отлично сочетается с C-подобным кодом (который доступен из C ++).

Единственная полноценная архитектура компилятора для расширенной компиляции (насколько мне известно) - это компиляция Java Hotspot и архитектуры с аналогичными принципами с использованием многоуровневой компиляции (Java Azul systems, популярный на сегодняшний день JS-движок JaegerMonkey).

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

Полиморфное встроенное кэширование (это означает, что если вы запустите первый цикл с некоторыми типами, второй раз, код цикла будет специализированными типами, которые были из предыдущего цикла, и JIT поставит охрану и поместит в качестве ветки по умолчанию встроенные типы, и на его основе из этой специализированной формы с использованием механизма SSA -форм будет применяться постоянное сворачивание / распространение, встраивание, мертвый- Оптимизация исключения кода и зависит от того, насколько «продвинутой» является JIT, будет улучшать или менее улучшать назначение регистров ЦП.) Как вы могли заметить, JIT ( hotspots) улучшит в основном ветвящийся код, а информация о времени выполнения станет лучше, чем код C ++, но статический компилятор, имеющий на стороне время для анализа, переупорядочивания инструкций для простых циклов, вероятно, получит немного лучшую производительность . Кроме того, как правило, в коде C ++ области, которые должны быть быстрыми, обычно не являются ООП, поэтому информация об оптимизации JIT не принесет такого впечатляющего улучшения.

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

Позвольте мне уточнить: предположим, что у вас есть базовый класс A, и у вас есть только одна его реализация, а именно B в другом пакете / сборке / драгоценном камне / и т. Д. и загружается динамически.

JIT, поскольку он видит, что B является единственной реализацией A, он может заменить везде во внутреннем представлении вызовы A кодами B, и вызовы методов не будут выполнять отправку (см. Vtable) но будут прямые звонки. Эти прямые вызовы также могут быть встроены. Например, у этого B есть метод: getLength(), который возвращает 2, все вызовы getLength() могут быть полностью уменьшены до константы 2. В конце код C ++ не сможет пропустить виртуальный вызов B из другой dll.

Некоторые реализации C ++ не поддерживают оптимизацию для большего количества файлов .cpp (даже сегодня в последних версиях GCC есть флаг -lto, который делает это возможным). Но если вы разработчик C ++ и беспокоитесь о скорости, вы, скорее всего, поместите все конфиденциальные классы в одну статическую библиотеку или даже в один и тот же файл, чтобы компилятор мог аккуратно встроить их, создав дополнительную информацию, которая есть у JIT по дизайну. , который должен быть предоставлен самим разработчиком, поэтому без потери производительности.

person Ciprian Mustiata    schedule 21.11.2010

Visual Studio имеет возможность выполнять профилирование во время выполнения, которое затем можно использовать для оптимизации кода.

«Профильная оптимизация»

person Keith Nicholas    schedule 23.04.2009
comment
-1 это не оптимизация времени выполнения, все это статически основано на поведении во время выполнения, вы облажались, если поведение изменится в другой среде выполнения - person Quonux; 12.10.2013

Microsoft Visual Studio называет это «оптимизацией под руководством профиля». ; вы можете узнать об этом больше на MSDN. По сути, вы запускаете программу несколько раз с прикрепленным профилировщиком для записи ее горячих точек и других характеристик производительности, а затем вы можете передать выходные данные профилировщика в компилятор, чтобы получить соответствующую оптимизацию.

person Crashworks    schedule 23.04.2009
comment
Умм .. Есть ли другие скрытые нелицензированные микробы? - person PedroMorgan; 17.11.2010

Я считаю, что LLVM пытается сделать что-то из этого. Он пытается оптимизировать все время существования программы (время компиляции, время компоновки и время выполнения).

person Zifre    schedule 24.04.2009
comment
LLVM намеревается сделать это в будущем, но они только недавно перевели свой компилятор C ++ в надежное состояние, и здесь нет JIT или оптимизации времени выполнения. Тем не менее, он может выполнять оптимизацию по профилю. - person BinarySplit; 17.11.2010

Резонный вопрос - но с сомнительной предпосылкой.

Как и в ответе Нильса, иногда «оптимизация» означает «оптимизацию низкого уровня», что само по себе является приятной темой.

Тем не менее, он основан на концепции «горячей точки», которая не имеет ничего общего с той актуальностью, которую ему обычно придают.

Определение: горячая точка - это небольшая область кода, в которой счетчик программы процесса тратит большую часть своего времени.

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

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

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

См. это.

person Mike Dunlavey    schedule 24.04.2009
comment
Придаете ли вы какое-либо значение представлению о том, что (некоторые) методы JIT (в широком смысле) ускоряют программы на Java, C # и т. Д.? Если да, то знаете ли вы о каких-либо попытках применить эти методы к статически компилируемым языкам или, альтернативно, причину, по которой статически компилируемые языки не могут извлечь выгоду из этих методов? - person Thomas L Holaday; 25.04.2009
comment
@ Томас: Быстрее чего? Интерпретируемый байт-код? Конечно. Другой статический скомпилированный код? Возможно, маргинальное, если можно будет использовать знания времени выполнения. Тем не менее, это случай поиска ключей под уличным фонарем. У клавиш более высокая производительность, а у уличного фонаря - низкоуровневая оптимизация. - person Mike Dunlavey; 25.04.2009