Подтвердить что ты не робот

Почему механизм выполнения LLVM быстрее, чем скомпилированный код?

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

  • Запустить его автоматически. Этот режим компилирует код в LLVM и использует JIT ExecutionEngine для компиляции его в машинный код "на лету" и запускает его без создания выходного файла.
  • Скомпилируйте его и запустите отдельно. В этом режиме выводится LLVM.bc файл, который я вручную оптимизирую (с помощью opt), скомпилируйте для собственной сборки (с llc) компиляцию в машинный код и ссылку (с помощью gcc) и запустите.

Я ожидал, что подход № 2 будет быстрее, чем подход №1, или, по крайней мере, с той же скоростью, но, пройдя несколько тестов скорости, я с удивлением обнаруживаю, что # 2 последовательно работает примерно в два раза медленнее. Это огромная разница в скорости.

В обоих случаях работает тот же исходный код LLVM. С подходом № 1 я еще не потрудился запускать любые пропуски оптимизации LLVM (именно поэтому я ожидал, что это будет медленнее). При подходе # 2 я запускаю opt с -std-compile-opts и llc с помощью -O3, чтобы максимизировать оптимизацию, но она не приближается к № 1. Вот пример запуска той же программы:

  • # 1 без оптимизации: 11.833s
  • # 2 без оптимизации: 22.262s
  • # 2 с оптимизацией (-std-compile-opts и -O3): 18.823s

Выполняет ли ExecutionEngine что-то особенное, о котором я не знаю? Есть ли способ оптимизировать скомпилированный код для достижения той же производительности, что и ExecutionEngine JIT?

4b9b3361

Ответ 1

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

Этот эффект также был отмечен с помощью виртуальных машин Java и Python PyPy VM, среди прочих.

Ответ 2

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

Как реальный пример, позвольте рассмотреть Google Native Client - я имею в виду оригинальный подход к сборке nacl, а не LLVM (потому что, насколько мне известно, в настоящее время существует направление поддержки как "nativeclient", так и "LLVM bitcode", (modyfied)).

Как вы можете видеть на презентациях (посмотрите на youtube.com) или в papers, как этот Собственный клиент: Песочница для портативного, ненадежного родного кода x86, даже их метод выравнивания делает размер кода более крупным, в некоторых случаях такое выравнивание инструкций (например, с помощью noops) дает лучший кэш.

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

Надеюсь, что этот ответ дает представление о том, сколько обстоятельств может повлиять на выполнение кодовой скорости, и это множество возможных причин для разных фрагментов кода, и каждый из них нуждается в исследовании. Nevermore, это интересная тема, поэтому, если вы найдете более подробную информацию, не повторяйте свой ответ и сообщите нам в "Post-Scriptorium", что вы нашли больше:). (Может быть, ссылка на whitepaper/devblog с новыми выводами:)). Тесты всегда приветствуются - посмотрите: http://llvm.org/OpenProjects.html#benchmark.