Вот уже довольно продолжительное время (начиная, пожалуй, с 7-й версии компиляторов Intel C++/Fortran) мы изучаем производительность задач тестового набора SPEC CPU2000 на различных платформах, применяя так называемую «профилирующую оптимизацию» кода при компилировании тестовых задач (ее полное название «оптимизация по профилю приложения», Profile-Guided Optimization, PGO). Применяли мы ее практически «на автомате», то есть как бы подразумевая, что такая оптимизация непременно приведет к созданию более производительного машинного кода. Тем не менее, не помешает, по крайней мере один раз, удостовериться, а так ли оно на самом деле? Учитывая, к тому же, что компиляторы Intel изначально рассчитаны для достижения максимальной производительности кода только на процессорах одноименного производителя (было бы странно, если бы было наоборот), тогда как используем-то мы эти задачи и для тестирования процессоров конкурентов :). В связи с этим, в нашем сегодняшним тестировании примут участие два «более-менее топовых» одноядерных процессора ведущих производителей-конкурентов Intel Pentium 4 660 и AMD Athlon 64 4000+. А пока вкратце рассмотрим суть метода оптимизации, именуемого «оптимизацией кода по профилю приложения» для краткости, здесь и далее будем называть такую оптимизацию «профилирующей оптимизацией».
Профилирующая оптимизация кода в Intel C++/Fortran Compiler 9.0
Профилирующая оптимизация кода (PGO) заключается в предоставлении «подсказок» компилятору о том, какие области кода приложения исполняются наиболее часто. Учитывая эту информацию, компилятор способен осуществлять более селективную и специфическую оптимизацию этих участков кода. Включение PGO в рассматриваемых компиляторах Intel C++/Fortran 9.0 позволяет получить следующие основные преимущества:
- Оптимизацию использования регистров процессора для хранения данных;
- Оптимизацию предсказания ветвлений посредством экспериментального определения наиболее характерных путей исполнения кода программы (которые невозможно определить с высокой достоверностью на этапе компиляции приложения);
- Предотвращение «разворачивания» малых циклов, исполнение которых требует всего нескольких итераций;
- Оптимизацию автоматического встраивания (инлайнинга) функций.
Создание «профилированных» приложений происходит в 3 этапа. На первом этапе «инструментирующей компиляции» создается так называемая «инструментирующая программа», которая включает в себя пользовательский исходный код и специальный код профилирования приложения, созданный компилятором. Второй этап «инструментирующего выполнения» заключается в запуске созданного «инструментирующего» приложения. При каждом запуске (с заданным набором входных данных) приложение создает профиль своего исполнения, который используется на третьем этапе «компиляции с обратной связью». На этом этапе компилятор, используя объединенную информацию из файлов-профилей исполнения приложения, производит оптимизацию наиболее часто исполняемых участков кода приложения.
Итак, ключевой фактор профилирующей оптимизации это выяснение наиболее часто исполняемых частей кода (как условно, так и безусловно). Таким образом, успех оптимизации по профилю во многом зависит от сходности задач (наборов входных данных), исполняемых приложением, подлежащем оптимизации. Действительно, похожие задачи, скорее всего, будут часто использовать одни и те же пути исполнения кода, в то время как сильно различающиеся задания могут в принципе вызывать совершенно разные процедуры приложения (в зависимости от степени сложности создаваемого приложения и области его применимости). Что касается задач SPEC, здесь реализуется практически идеальный сценарий: входные данные, используемые для профилирования приложений, представляют собой «уменьшенные наборы» входных данных, используемых для последующего измерения производительности тестовых задач. Тем не менее, не следует забывать, что в общем случае это может быть далеко не так.
Результаты тестирования
В тестах использовались следующие версии компиляторов:
- Intel(R) C++ Compiler for 32-bit applications, Version 9.0 Build 20050912Z Package ID: W_CC_C_9.0.024
- Intel(R) Fortran Compiler for 32-bit applications, Version 9.0 Build 20050912Z Package ID: W_FC_C_9.0.024
В случае оптимизации кода с профилированием, как обычно, использовались следующие общие ключи компилятора:
PASS1_CFLAGS= -Qipo -O3 -Qprof_gen
PASS2_CFLAGS= -Qipo -O3 -Qprof_use
При компиляции кода без профилирующей оптимизации набор ключей был более простым:
COPTIMIZE= -Qipo -O3
Pentium 4 660
Начнем с рассмотрения результатов на «родном» для компиляторов Intel процессоре Pentium 4 660. Резонно предположить, что максимальную выгоду от профилирующей оптимизации следует ожидать именно в этом случае.
Практически во всех случаях, целочисленные задачи SPEC CPU2000 получают преимущество от профилирующей оптимизации кода (максимум до 30%). Исключение составляют лишь 164.gzip и 256.bzip2 (почти всегда демонстрирующие незначительную потерю производительности), 175.vpr (показывающая столь же небольшой прирост производительности), а также 181.mcf, результат профилирующей оптимизации которой зависит от варианта оптимизации кода. При использовании вариантов кода «без оптимизации», с оптимизацией под SSE (-QxK) и SSE2 (-QxW) (заметим, что об SSE/SSE2 в чистом виде здесь говорить не приходится используются только целочисленные инструкции) выигрыш от профилирующей оптимизации составляет не более 0.7%, тогда как включение оптимизаций, специфических для процессора Pentium 4 (-QxN и -QxP), увеличивает этот показатель до 25%. Можно предположить, что подобный результат связан с крайне удачным использованием специфических префиксов-подсказок исполнения условных переходов (branch hit prefixes, 2Eh и 3Eh) в случае этой задачи (впрочем, это лишь наше предположение в документации на компиляторы Intel C++/Fortran Compiler 9.0 не упоминается никаких подробностей относительно конкретно используемых оптимизациях кода в том или ином случае). Отметим, что некоторый дополнительный выигрыш (до 10%) при использовании вариантов -QxN и -QxP показывает также задача 252.eon.
Усредненный результат тестов оценка SPECint_base2000, выраженная по отношению к производительности кода без профилирующей оптимизации, наглядно демонстрирует выигрышность последней для целочисленных задач SPEC CPU2000 на процессоре Pentium 4 прирост производительности составляет от 7 до 10%, в зависимости от используемого варианта оптимизации кода (естественно, в пользу специфических для Pentium 4 оптимизаций -QxN и -QxP).
Из наших предыдущих исследований различных платформ в SPEC CPU2000 уже хорошо известно, что, в отличие от целочисленных задач, задачи SPEC с вещественными числами обычно дают намного менее однозначную картину. Так обстоит дело и при сопоставлении вариантов кода с профилирующей оптимизацией и без нее, даже на «родном» процессоре Pentium 4.
Тем не менее, более-менее однозначную общую картину можно получить и в этом случае. Итак, наибольший выигрыш от профилирующей оптимизации получают задачи 177.mesa (до 22%), 168.wupwise (до 16%, если не считать отрицательный результат варианта -QxK) и 187.facerec (до 11%), некоторое преимущество наблюдается и в задачах 191.fma3d и 301.apsi (до 3.5%). В остальных случаях наблюдается либо практически нулевой, либо… парадоксальный результат как, например, в 179.art, внезапно показавшей 20% снижение производительности в одном из наиболее близких для Pentium 4 вариантов -QxN, и 8% прирост в другом из них (-QxP).
Общий результат SPECfp_base2000, благодаря практически полному отсутствию «провалов» по отдельным подтестам, также оказывается несколько выигрышным от 1.3% (наименее удачный вариант, -QxK) до 3.9% (вариант -QxP, прямо соответствующий процессорному ядру Prescott).
Как и ожидалось, профилирующая оптимизация кода в компиляторах Intel C++/Fortran Compiler 9.0 действительно продемонстрировала преимущества в скорости при исполнении кода задач на процессоре одноименного производителя Intel Pentium 4 660. Посмотрим теперь, как обстоит дело на процессоре конкурента AMD Athlon 64 4000+. В приведенных ниже диаграммах не фигурирует вариант кода -QxP, поскольку инструкции SSE3 данным процессором не поддерживаются. Тем не менее, аналогичного ему варианта -QxN (который, согласно документации компиляторов, поддерживается только процессорами Pentium 4) будет вполне достаточно для понимания общей картины.
Сколь бы удивительным это ни казалось, в целочисленных тестах SPEC CPU2000 на качественном уровне наблюдается примерно та же картина, что и на процессоре Pentium 4, а на количественном уровне выигрыш от профилирующей оптимизации даже превосходит результат, полученный на Pentium 4. Так, максимальный прирост составляет целых 49%. Распределение результатов по задачам сохраняется: наименьшую выгоду получают 164.gzip, 175.vpr и 256.bzip2. Задача 181.mcf вновь показывает преимущество только в специфическом варианте -QxN, вероятно, использующем наряду с прочими оптимизациями префиксы-подсказки для исполнения условных переходов. В этом же варианте достигается наилучший результат в большинстве других задач, а также интегральной оценке SPECint_base2000 15% прироста, по сравнению примерно с 9-10%-ным приростом в остальных случаях.
Результаты тестов с вещественными числами на Athlon 64 обладают наименьшей однозначностью. Можно отметить, в частности, значительное количество «выпадений» результатов одного из вариантов (в особенности, варианта «без оптимизации») из общего ряда, сколь бы то ни было разумного объяснения которым придумать достаточно трудно. Если говорить о наилучшем и наихудшем результатах, в целом они совпадают с таковыми, полученными на процессоре Pentium 4. А именно, наибольший прирост скорости «в среднем» получает задача 177.mesa, а наибольшее падение производительности показывает 179.art.
Достаточно однозначную оценку нельзя дать и для усредненного показателя SPECfp_base2000, выраженного в относительных единицах он сильно зависит от варианта оптимизации кода и находится в интервале от -3.3% до 1.4%. В первом приближении можно считать, что выгода от профилирующей оптимизации в этом случае близка к нулю.
Заключение
Результаты нашего исследования показывают, что использование двухпроходного компилирования кода задач тестового пакета SPEC CPU2000 в Intel C++/Fortran Compiler 9.0 по профилю приложения в целом является более выигрышным вариантом в плане производительности по сравнению с обычным, однопроходным компилированием. Выгода от профилирующей оптимизации наблюдается при исполнении кода как на «родном» для компиляторов Intel процессоре Intel Pentium 4, так и на процессоре основного конкурента AMD Athlon 64. Наибольший выигрыш в скорости показывают целочисленные задачи SPEC CPU2000, тогда как задачи с вещественными числами демонстрируют менее однозначный результат.
Таким образом, использование «профилированного» варианта кода тестов SPEC CPU2000 для оценки производительности различных платформ с процессорами различных производителей можно считать оправданным, а многочисленные результаты такого тестирования достоверными.