Не так давно мы рассмотрели поведение новой версии компиляторов Intel C++/Fortran 9.0 в тестах SPEC CPU 2000. Мы изучали производительность лишь 32-битной (x86) версии кода, генерируемого этими компиляторами — с одной стороны, по той причине, что это наиболее интересно, ибо 32-битные приложения на сегодня по-прежнему составляет большую долю программного обеспечения. С другой стороны — потому что… нам просто долгое время не удавалось получить каких-либо результатов на, казалось бы, родном для Intel EM64T-кода процессоре Pentium 4 670.
Среди процессоров, участников нашего сегодняшнего тестирования всего два — по понятным причинам, с поля боя ушел Pentium M 770, не поддерживающий 64-битные расширения в принципе. Для AMD Athlon 64 FX-57 нам вновь потребовалась «доработка» кода — в данном случае, 64-битного, с которым наша утилита ICC Patcher прекрасно умеет справляться. В случае же Pentium 4 670 нам потребовалась… «модификация» совсем иного рода. Дело в том, что данный процессор, работающий под управлением Windows XP x64 Edition, наотрез отказывался выполнять задачи SPEC CPU2000, откомпилированные под EM64T — не проходило и нескольких минут, независимо от вида задачи и используемых оптимизаций, как система уходила на перезагрузку. Чем была вызвана подобная несовместимость процессора Intel с родным EM64T-кодом, на некоторое время было совершенно непонятно, пока мы не понизили частоту процессора до минимальной — 2.8 ГГц, подозревая, что дело в перегреве (ведь для того, чтобы последний не мог повлиять на результаты тестов в худшую сторону за счет троттлинга, нам приходилось, идя вразрез с принципами Intel, отключать автоматическую защиту процессора от перегрева — Thermal Monitor 1 или 2). На этой частоте все тесты прошли на ура, однако в итоге оказалось, что дело-таки не в перегреве — а просто в… неработоспособности имеющегося у нас инженерного сэмпла Pentium 4 670 на номинальной частоте 3,8 ГГц, причем, заметим — именно в 64-битном режиме. Уже на частоте 3,6 ГГц процессор оказался полностью работоспособен по отношению к EM64T-коду, в связи с чем и участвует в нашем сегодняшнем исследовании под видом «Pentium 4 660».
Чтобы сравнение было осмысленным, мы использовали те же версии компиляторов, что и ранее, при изучении производительности 32-битного кода (хотя с того момента уже были выпущены более новые версии):
- Intel(R) C++ Compiler for 32-bit applications, Version 9.0 Build 20050624Z Package ID: W_CC_C_9.0.020
- Intel(R) C++ Compiler for Intel(R) EM64T-based applications, Version 9.0 Build 20050624 Package ID: W_CC_C_9.0.020
- Intel(R) Fortran Compiler for 32-bit applications, Version 9.0 Build 20050624Z Package ID: W_FC_C_9.0.019
- Intel(R) Fortran Compiler for Intel(R) EM64T-based applications, Version 9.0 Build 20050624 Package ID: W_FC_C_9.0.019
Во всех случаях (32/64-битный код с различными вариантами оптимизации), как обычно, использовались одинаковые общие ключи компиляции кода:
PASS1_CFLAGS= -Qipo -O3 -Qprof_gen
PASS2_CFLAGS= -Qipo -O3 -Qprof_use
Что касается специфических вариантов оптимизации кода, их всего три, т.к. остальные варианты EM64T-версией компилятора не допускаются:
- Без оптимизации (случай, когда никаких ключей вида -Qx* не указано);
- Оптимизация под SSE2 (-QxW, использование «нового» варианта оптимизации под ядро Northwood, -QxN, для компиляции EM64T-кода по каким-то непонятным причинам не допускается);
- Оптимизация под SSE3 (-QxP).
Прежде чем перейти к сравнению реальных цифр, отметим также, что прямое сравнение производительности первого варианта кода «без оптимизации» на платформах x86 и x86-86/EM64T, честно говоря, некорректно. Это связано с тем, что в первом случае (x86) данный код использует инструкции FPU, в то время как тот же самый код, откомпилированный под EM64T, использует инструкции SSE/SSE2, ввиду отсутствия поддержки нативного x87 FPU-кода в этой платформе.
Pentium 4 660
Итак, начнем с рассмотрения результатов тестов на процессоре Pentium 4 670, искусственно превращенном в Pentium 4 660 (частота ядра 3.6 ГГц, механизм TM1/TM2 отключен). Для наглядности, ниже по тексту мы приводим только относительные результаты сравнения производительности 64-битного кода с 32-битным. С абсолютными результатами тестов в чистом виде можно ознакомиться в Приложении №1 к настоящей статье.
Целочисленные тесты SPEC CPU2000. Как обычно, можно выделить несколько групп подтестов. Первая из них — это задачи, относящиеся к «64 битам» более-менее равнодушно, т.е. не показывающие большой прирост или, напротив, снижение производительности по сравнению с 32-битным кодом (164.gzip, 175.vpr, 176.gcc). Ко второй группе можно отнести подтесты, показывающие незначительный прирост (примерно 5%) — задачи 255.vortex и 256.bzip2. Более значительный прирост в скорости (от 15 до 33%) наблюдается в задачах 186.crafty, 197.parser и 252.eon. Тем не менее, среди SPECint2000 есть и задача, в которой наблюдается не меньшее (и даже большее — до 40% в случае оптимизации под ядро Prescott и SSE3) падение производительности — это 181.mcf. Наконец, не менее интересную группу составляют задачи, в которых наблюдается либо прирост, либо снижение скорости, в зависимости от варианта оптимизации кода — это 253.perlbmk, 254.gap и 300.twolf.
Общий итог по оценке SPECint_base2000 таков: использование EM64T в целом является выигрышным, но сам выигрыш довольно невелик — от 1.6 до 3.8%.
Гораздо интереснее выглядит сравнение тестов с вещественной арифметикой. Прежде всего, бросается в глаза выигрышность 64-битного варианта кода «без оптимизации» практически во всех случаях (за исключением, разве что, задачи 171.swim). Здесь сказывается именно то обстоятельство, о котором мы писали выше — переход от 32-разрядной к 64-разрядной версии сопровождается переходом от использования 8 регистров FPU, имеющих неудобную стековую организацию, к использованию 16 линейных регистров SSE/SSE2. Т.е. результат получается не «выигрышем от 64 бит в чистом виде». Именно поэтому корректнее сравнивать лишь результаты вариантов -QxW и -QxP, в которых в обоих случаях используются линейные SSE/SSE2-регистры (8 и 16, соответственно).
Практически нечувствительными к «64 битам» (не показывающими ни заметного выигрыша, ни падения производительности) являются довольно много подтестов — 171.swim, 178.galgel, 188.ammp, 179.lucas и 200.sixtrack. Небольшое преимущество в 64-битном режиме показывают задачи 187.facerec (5.7-6.1%), 191.fma3d (4.1-5.6%) и 301.apsi (5.0-5.4%). Производительность остальных задач весьма сильно зависит от варианта оптимизации кода (SSE2 или SSE3). Так, 168.wupwise ощутимо выигрывает только при использовании SSE3 (12.5%), но немного проигрывает в случае SSE2-кода (-2.4%). Для 172.mgrid больший прирост в скорости, напротив, наблюдается именно в SSE2-варианте (27.6%). Задача 177.mesa показывает сопоставимый прирост в обоих случаях (27.1% и 20.8%, соответственно). Однако наиболее шокирующим является поведение подтеста 179.art. Если в случае SSE3 наблюдается более-менее объяснимый прирост в 34.7%, SSE2-вариант ведет себя воистину фантастически, демонстрируя 140.5% увеличение производительности при использовании 64-битного кода. Списать это на случайность невозможно — на Athlon 64 FX-57 также наблюдается весьма значительный выигрыш в скорости. Поэтому остается только предположить, что подобное поведение может быть вызвано тем, что 32-битный компилятор Intel генерирует очень неоптимальный код этой задачи при использовании SSE2-оптимизации. Для последней из рассматриваемых задач SPECfp, 183.equake, нам удалось получить результат лишь в случае SSE2-кода (прирост составил 13.7%). Благополучно скомпилировать 64-битный SSE3-вариант не удалось — скомпилированный на первом проходе код выдавал ошибки на стадии профилирования, независимо от используемого процессора. В связи с этим, к сожалению, не удалось получить и общую оценку SPECfp_base2000 для случая -QxP. В случае же -QxW средний прирост в скорости при переходе к 64-разрядному коду составил 13.7%. Интересно отметить, что практически тот же результат (13.4%) показал и неоптимизированный вариант кода, прямое сравнение производительности 32- и 64-разрядных версий которого, как мы уже говорили выше, не вполне корректно.
Athlon 64 FX-57
Переходим ко второму участнику нашего сравнительного тестирования — процессору Athlon 64 FX-57, поддерживающему все необходимые для осуществления тестов расширения (x86-64, SSE2 и SSE3).
В целочисленных тестах разница между 32- и 64-битным кодом выглядит более выраженной по сравнению с тестами на процессоре Intel. Прежде всего, здесь практически отсутствуют «смешанные» результаты — когда одна и та же задача показывает либо преимущество, либо недостаток в производительности, в зависимости от варианта оптимизации кода (за исключением, разве что, одной-единственной 164.gzip). Не наблюдается также индифферентных результатов (отсутствия заметного выигрыша или проигрыша в скорости) — так, задачи 175.vpr, 176.gcc и 300.twolf показывают большее снижение производительности в 64-разрядном варианте, а 255.vortex и 256.bzip2, напротив, находятся в большем выигрыше от «64 бит», по сравнению с поведением этих задач на Pentium 4. Наибольший выигрыш (пусть и с другими абсолютными показателями и распределением результатов), тем не менее, по-прежнему наблюдается в задачах 186.crafty, 197.parser и 252.eon, а наибольшее снижение производительности демонстрирует 181.mcf. Что самое интересное — все это, вместе взятое, обеспечивает практически нулевую разницу в результатах в целом (по оценке SPECint_base2000) — от 0.3 до -0.6%.
Тесты с вещественной арифметикой также демонстрируют некоторые различия в поведении на 64-разрядных платформах Intel и AMD. Прежде всего, вариант кода «без оптимизации» на платформе AMD уже не выглядит выигрышным во всех случаях — т.е. как бы получается, что использование 8 FPU-регистров со стековой организацией может быть выгоднее, чем использование 16 линейных XMM-регистров. Скорее всего, виноваты не сами «регистры» (в смысле, их количество и задействуемые исполнительные модули процессора), а все же специфика генерации кода — не забываем, что разработчиком изучаемого нами компилятора все же является компания Intel, а не AMD :).
Рассмотрим подробнее варианты -QxW и -QxP, сравнение которых, как мы отмечали выше, выглядит более корректным, ибо представляет собой результат перехода от «32 бит» к «64 битам» в чистом виде. Итак, 168.wupwise показывает более сглаженный результат — от 3.2 до 6.3% прироста (ее поведение на платформе Intel отличалось несколько большей хаотичностью), 171.swim и 200.sixtrack здесь также не получают от «64 бит» практически ничего, 172.mgrid демонстрирует меньший прирост (3.8 — 10.8%), как и 177.mesa (4.8 — 9.0%). 173.applu на платформе AMD, напротив, ведет себя более хаотично (выигрывает 23.8% в случае SSE2-кода, но проигрывает 1.9% при оптимизации под SSE3). 178.galgel показывает больший прирост в варианте SSE2, но также почти не отличается по производительности от 32-битного варианта SSE3. Как мы уже отмечали выше, 179.art в SSE2-варианте также отличается фантастическим приростом скорости — 81.7%, и гораздо менее значительным (16.0%) в случае SSE3-кода. Задача 183.equake, работоспособный код которой нам удалось получить только для SSE2, на платформе AMD демонстрирует 3.6%-ное падение производительности. Тем не менее, есть и задачи, которые получают заметно больший прирост в скорости при переходе к 64-битному коду именно на платформе AMD — это 188.ammp, 189.lucas и 191.fma3d. Две оставшиеся задачи — 187.facerec и 301.aspi — демонстрируют примерно одинаковое увеличение производительности как на платформе Intel, так и AMD.
Наконец, нам осталось рассмотреть общий итог тестов SPECfp_base2000. В случае неоптимизированного кода прирост от «64-разрядности» (пусть и не в чистом виде) составляет всего 5.3% (это неудивительно, т.к. в этой категории присутствует немало тестов, получивших отрицательный результат в 64-разрядном режиме), в случае SSE2-варианта кода (-QxW) величина прироста составляет 11.6%, чем немного недотягивает до платформы Intel (13.7%).
Выводы
Основные выводы, которые можно сделать на основании полученных результатов, следующие.
1. Перевод целочисленного кода в 64-битный режим может быть как выигрышным, так и проигрышным с точки зрения производительности. Как всегда, все зависит от конкретной задачи — где-то можно получить до 30% прироста, а где-то — и до 40% падения в скорости. Тем не менее, результат «в среднем» является все же выигрышным, нежели проигрышным.
2. Задачи, использующие вычисления с плавающей точкой, в большинстве случаев получают преимущества в скорости при переходе от 32-разрядного (x86) к 64-разрядному (x86-64/EM64T) коду. Если проигрыш иногда и наблюдается, его показатель не велик — не превышает и 5%.
3. 64-разрядные процессоры AMD в целом демонстрируют несколько меньший выигрыш при использовании EM64T-кода по сравнению с 64-разрядными процессорами Intel. Разумеется, это ни в коем случае не может говорить об эффективности реализации «64 бит» в том или ином случае, а сообщает лишь о том, что EM64T-версия компиляторов Intel генерирует код, более оптимальный с точки зрения архитектуры P4, нежели K8. Собственно, ничего удивительного в этом нет, и было бы очень странно, если бы все было наоборот :).
Приложения
Приложение 1. Результаты тестов в числах