Оптимизация во время выполнения статических языков: JIT для C ++? - PullRequest
20 голосов
/ 23 апреля 2009

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

Ответы [ 7 ]

32 голосов
/ 23 апреля 2009

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

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

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

10 голосов
/ 23 апреля 2009

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

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

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

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

8 голосов
/ 21 ноября 2010

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

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

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

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

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

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

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

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

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

5 голосов
/ 23 апреля 2009

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

5 голосов
/ 23 апреля 2009

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

"Оптимизация по профилю"

3 голосов
/ 24 апреля 2009

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

2 голосов
/ 24 апреля 2009

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

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

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

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

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

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

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

Смотрите это.

...