Стал ли JIT компилятор эффективнее?
Фахад Гилани
JIT компиляция — значимая часть технологии, открывающей возможности для широкого спектра оптимизаций. Хотя текущая версия по временным рамкам ограничена в количестве выполняемых оптимизаций, теоретически она может превзойти все существующие статические компиляторы. Конечно, причина в том, что динамические свойства критичного к быстродействию кода или его контекст не полностью известны или проверены до периода выполнения. JIT компилятор способен использовать собранную информацию, генерируя более эффективный код, который теоретически может заново оптимизироваться при каждом запуске. Сейчас компилятор генерирует машинный код лишь раз для каждого метода. После генерации машинный код выполняется с той скоростью, на которую способен данный компьютер. В научном программировании это может быть удобно. Код научных расчетов в основном выполняет операции над числами. Чтобы осуществлять такие вычисления за приемлемое время, следует корректно использовать некоторые аппаратные ресурсы. Хотя многие статические компиляторы хорошо оптимизируют код, динамическая природа JIT компилятора позволяет ему оптимизировать использование ресурсов с помощью таких методов, как распределение регистров на основе приоритета (priority-based register allocation), «ленивая» выборка кода (lazy code selection), подстройка кэша (cache-tuning) и специфичные для процессора оптимизации (CPU-specific optimizations). Эти методы также открывают возможности для более тонкой оптимизации вроде разложения сложных команд (strength reduction), подстановки значений констант (constant propagation), избыточной загрузки после записи (redundant load-after-store), исключение общих подвыражений (common sub-expression elimination), исключение проверки границ массивов (array bounds check elimination), подстановки тел методов (method inlining) и т. д. Хотя для JIT компилятора такие продвинутые виды оптимизации возможны, в текущей версии.NET они не поддерживаются. В прошлом среди программистов была распространена практика самостоятельной оптимизации кода, выполняемого на определенной машине, под нижележащую архитектуру (например за счет программной конвейеризации или работы с кэшем вручную). Запуск того же кода на другой машине с другим аппаратным обеспечением требовал модификации исходного кода под новое оборудование. Со временем процессоры стали иначе выполнять код, используя специальные встроенные инструкции и технологии. Теперь эти оптимизации могут быть задействованы JIT компилятором без модификации существующего кода. В результате код, выполняемый на рабочей станции, может обладать не меньшим быстродействием и на домашнем компьютере с совершенно другой архитектурой. JIT компилятор, поставляемый с.NET Framework 1.1, значительно усовершенствован в сравнении со своим предшественником версии 1.0. График на рис. 1 иллюстрирует сравнение производительности между CLR версии 1.0 и 1.1 при выполнении комплекса эталонных тестов SciMark 2.0 на двух платформах. Для тестирования использовалась машина с процессором Pentium 4 с тактовой частотой 2.4 ГГц и 256 МБ оперативной памяти.
Рис. 1. Усовершенствование JIT в.NET 1.1 Эталонные тесты SciMark состоят из нескольких ядер, моделирующих наиболее распространенные в научных приложениях вычислительные операции; каждое из них имеет свои особенности в доступе к памяти и наборе операций с плавающей точкой. Вот эти ядра: быстрые преобразования Фурье (Fast Fourier Transformations, FFT), итерации последовательных сверхрелаксаций (Successive Over-Relaxation iterations, SOR), квадратура Монте-Карло (Monte-Carlo quadrature), умножение разреженных матриц (sparse matrix multiplications) и разложение плотных матриц на множители (dense matrix factorization) для решения комплексных линейных систем. SciMark изначально разработан на Java (math.nist.gov/scimark (EN)) и перенесен на C# Крисом Ри (Chris Re) и Венером Вогелсом (Wener Vogels). Следует заметить, что эта реализация не использует небезопасный код (unsafe code), который мог бы дать небольшое приращение скорости примерно на 5—10%. Рис. 1 отражает общий результат в миллионах операций с плавающей точкой в секунду (MFLOPS) для двух версий.NET Framework. Это должно дать вам представление о быстродействии текущей версии (1.1) и о вероятном его повышении в будущих версиях. График показывает, что CLR 1.1 весьма значительно превосходит версию 1.0 (а именно на 54.1 MFLOPS). Версия 1.1 сочетает в себе ряд усовершенствований в общей реализации, в том числе добавление в JIT компилятор оптимизаций, специфичных для целевой архитектуры, вроде приведения double к integer с использованием инструкций SSE2 в процессорах с архитектурой IA-32. Разумеется, компилятор генерирует оптимизированный код и для других процессоров. Думаю, что следующая версия JIT компилятора будет работать еще лучше, и способность JIT компиляторов генерировать более быстрый код, чем создаваемый статическими компиляторами, — лишь вопрос времени.
|