2003 г
Парадоксальные результаты первого тура тестирования побудили меня обратиться к изучению вопроса о том, а насколько же оптимизация реально влияет на производительность? Результаты исследования оказались столь поразительными, что я не могу не поделиться ими на этих страницах. Однако начну по порядку.
Исследования проводились на моей домашней машине — напомню, это P4/2,53 с 533-мегагерцной шиной, мама на i845PE, памяти — 1 Гбайт (2×512, DDR333), «несущий» винт — Seagate Barracuda IV о 40 Гбайт, прочие компоненты несущественны. Дистрибутив — в девичестве Archlinux 0.5, подвергнутый мною многочисленным операциям по смене пола, наращиванию одними членами и усекновению — других. В итоге чего в нем образовались: ядро 2.4.22 и gcc
3.3.1. Последний был сконфигурирован (вывод команды gcc -v
) так:
Configured with: ../gcc-3.3.1/configure --prefix=/usr --enable-shared --enable-languages=c,c++ --enable-threads=posix --with-slibdir=/lib --enable-_cxa_atexit --enable-clocale=gnu Thread model: posix
А собран командой
$ make bootstrap
со следующими флагами:
CFLAGS="-O3 -march=pentium4 -fomit-frame-pointer -funroll-loops -pipe -mfpmath=sse -mmmx -msse2 -fPIC" CXXFLAGS="$CFLAGS" BOOTSTRAPCFLAGS="$CFLAGS"
внесенными в профильный файл (/root/.zshrc
).
Тратить особо много времени мне не хотелось — некоторым образом и другие дела есть (однако, забегая вперед, замечу, что в итоге я провозился с этими тестами намного дольше, чем рассчитывал). Поэтому я решил выбрать одну, но представительную, задачу. А именно: кодирование WAV -> MPEG программой lame
(текущая на тот момент версия 3.92). То есть один из тех классических тестов, на которых всегда демонстрировалось превосходство Pentium 4 над всеми остальными x86-совместимыми архитектурами (на другие тесты, типа преобразования MPEG -> DivX и тому подобное, я потенции в себе не находил).
В качестве объекта для истязания я выбрал wav-файл размером около 750 Мбайт, представляющий собой оцифровку записи концерта Юрия Визбора в альплагере Цей, выполненную с магнитофонной ленты Владимиром Поповым (за что ему — искренняя благодарность). Это я к тому, что никаких проприетарных дисков я не граббил:-)
Итак, для начала я собрал lame в конфигурации по умолчанию. А нужно заметить, что эта самая умолчальная конфигурация предусматривает следующие флаги (их можно подсмотреть в файле ~/lame-xx/configure.in):
-O3 -fomit-frame-pointer -ffast-math -funroll-loops -Wall -pipe
То есть с высоким уровнем оптимизации (хотя, естественно, и без указания конкретного процессора). После этого я перекодировал свой wav-файл просто:
$ lame visbor.wav
Учитывая далеко не студийное качество записи исходного материала, с битрейтами и прочим я решил не возиться. Впрочем, по умолчанию в lame предполагаются достаточно высокие параметры — mpeg layer 1, 44,1 kHz, 128 kbps, qual=2. Результаты трех измерений (по выводу команды lame Real time, которое у меня и здесь, и во всех последующих случаях совпало с выводом CPU time) оказались, как можно видеть из таблицы 1, практически идентичными — 2 минуты 59 секунд в среднем.
Таблица 1
Сборка Gcc 3.3.1 | ||||
1 | 2 | 3 | Avg | |
Default -O3 | 00:03:00 | 00:02:59 | 00:02:59 | 00:02:59 |
-march=p4 with other | 00:03:26 | 00:03:26 | 00:03:27 | 00:03:26 |
-march=i686 | 00:02:55 | 00:02:55 | 00:02:55 | 00:02:55 |
-march=p4 only | 00:02:56 | 00:02:56 | 00:02:57 | 00:02:56 |
Сборка Gcc 3.3.2 | ||||
-march=p4 with other | 00:03:29 | 00:03:26 | 00:03:26 | 00:03:27 |
-O2 | 00:02:59 | 00:03:00 | 00:03:00 | 00:03:00 |
-O1 | 00:03:01 | 00:03:01 | 00:03:01 | 00:03:01 |
-O0 | 00:06:19 | 00:06:19 | 00:06:19 | 00:06:19 |
Пересобираю lame со своими обычными флагами (приведенными выше), которые включают специфичные для «четверки» инструкции и, довольно потирая руки, запускаю конвертацию по новой. Ожидая демонстрации мощи P-4, даже не отправляюсь курить. Тем больше было мое изумление, когда вижу результат — 3 минуты 26 секунд, то есть почти на полминуты худший. Второй и третий прогоны теста картины не меняют (см. табл. 1)…
Н-да, сказал я себе, и пересобрал lame
с теми флагами, которые используются при сборке Arch Linux вообще:
CFLAGS="-O3 -march=i686"
Здесь душа моя порадовалась — результаты оказались хоть и чуть-чуть, но получше умолчальных — 2 минуты 55 секунд, причем с идеальной воспроизводимостью (см. табл. 1).
Тут же появляется мысль пересобрать lame
, выкинув P4-специфичные флаги и оставив только
CFLAGS="-O3 -march=pentium4"
Результат отличился от предыдущего нечувствительно (однако все-таки в худшую сторону) — 2 минуты 56 секунд :-)…
Однако — подумал я, и решил сменить компилятор на ультрамодерновый gcc
3.3.2, тем более что все равно собирался это делать. Конфигурирую его и собираю точно тем же образом, что и предыдущий:
$ CFLAGS="-O3 -march=pentium4 -fomit-frame-pointer -funroll-loops -pipe -mfpmath=sse -mmmx -msse2 -fPIC" Configured with: ../gcc-3.3.2/configure --prefix=/usr --enable-shared --enable-languages=c,c++ --enable-threads=posix --with-slibdir=/lib --enable-_cxa_atexit --enable-clocale=gnu Thread model: posix gcc version 3.3.2 $ make bootstrap
Повторяю процедуру кодирования троекратно — с идеальным воспроизведением предыдущего, далеко не идеального, результата — 3 минуты 27 секунд (то есть даже с тенденцией к ухудшению). Поскольку значимых отличий между компиляторами не обнаруживается, дальнейших упражнений с процессорно-специфическими флагами решаю не производить. Вместо этого мне приходит в голову мысль проверить, как скажется на скорости кодирования сборка просто с разными уровнями оптимизации.
Решено — сделано, последовательно пересобираю lame только с флагами
CFLAGS="-O2"
затем
CFLAGS="-O1"
и, наконец,
CFLAGS="-O0"
то есть без всякой оптимизации. Сказалось, но весьма странным образом. Результаты для уровней -O2
и -O1
составили, соответственно, 3 минуты ровно и 3 минуты одна секунда. И только при -O0
я наконец смог удовлетворенно сказать то, что сказали русские мужики после того, как засунули в японскую бензопилу шестигранный лом (из уважения к, возможно, читающим это дамам повторять не буду): результат 6 минут 19 секунд продемонстрировал, что все-таки уровни оптимизации в gcc
придуманы были не зря. Что блестяще :-) демонстрирует сводная таблица средних значений (табл. 2) и построенная по ней диаграмма (рисунок).
Таблица 2
Табл. 2. Сравнение средних | |
-O0 | 00:06:19 |
-O1 | 00:03:01 |
-O2 | 00:03:00 |
-O3 | 00:02:59 |
-O3 -march=i686 | 00:02:55 |
-O3 -march=p4 only | 00:02:56 |
-O3 -march=p4 etc. | 00:03:26 |
Конечно, все сказанное относится к одной отдельно взятой (и довольно специфической) программе, выполнявшейся на единичном (и также довольно специфическом) процессоре. Тем не менее, некоторые предварительные выводы сделать можно.
А именно — наилучший результат с точки зрения быстродействия достигается при использовании сочетания флагов -O3
и -march=i686
(не случайно CRUX и Archlinux, которые собираются именно так, субъективно казались мне самыми быстрыми дистрибутивами из всего виденного). Впрочем, эффект от этого на практике можно почувствовать только при тотальном о-грабблении пары ящиков сидюков (да и то при условии их конвейерной подачи в привод). Различия же между уровнями оптимизации от -O1
до -O3
можно считать несущественными (рис. 1). Лишь полное отсутствие оптимизации (-O0
) ухудшает производительность в значительной мере (более чем в два раза). И — крайности смыкаются — тот же эффект, хотя и не столь выраженный, дает «оптимальная оптимизация» под Pentium 4 (что также имеет органолептическое подтверждение — заоптимизированные до посинения под «четверку» дистрибутивы типа Sorcerer сотоварищи на практике оказываются изрядно задумчивыми).
Конечно, интересно было бы посмотреть, имеет ли место такой эффект при экстремальной оптимизации под Athlon. С одной стороны, мои наблюдения более чем двухлетней давности показывают, что еще тогда gcc
оптимизировал под него лучше, чем под P4. С другой же — возможно, что в описанном и кроется причина провального результата Gentoo на mkisofs
, полученного в первом туре мегатестирования.
И еще один, косвенный (но приятный), вывод: кодирование посредством lame
обеспечивает практически идеальную воспроизводимость результатов. И, следовательно, эта программа должна стать непременным членом любого набора пользовательских тестов под Linux.
После сочинения всего сказанного выше я познакомился с
Для начала, руководствуясь советами Георгия «-march=где-то_рядом
и -O2
«, я пересобрал lame
с флагами
CFLAGS="-O2 -march=i686"
получив при этом, однако, чуть-чуть худшие результаты, чем при -O3 -march=i686
(2:57 и 2:55, соответственно).
Потом я вспомнил, что резонные люди советовали не собирать пакеты для Pentium 4 с использованием флага -march=pentium4
, а ограничиваться для этого флагом -march=pentium3
. Что ж, за нами не заржавеет, собираю lame
c
CFLAGS="-O3 -march=pentium3 -fomit-frame-pointer -funroll-loops -pipe -mfpmath=sse -mmmx -msse"
Результат измерений — 2:56. Далее, избавляюсь от вредоносного флага -funroll-loops
:
CFLAGS="-O3 -march=pentium3 -fomit-frame-pointer -pipe -mfpmath=sse -mmmx -msse"
Результат неизменно превосходен — 2 минуты 56 секунд :-)
Вспоминаю историю о насморке: как известно, без врачебного вмешательства он длится целую неделю, а при лечении квалифицированным врачом — проходит всего за семь дней. И дальнейшие эксперименты прекращаю. Не забыв, однако, составить новую сводную таблицу средних значений (табл. 2) и соответствующий ей график (для пущей сопоставимости с lame-тестом на Athlon’е, который был выполнен позже).
Таблица 3
Таблица. Результаты lame-теста для P4 | |
-O0 | 00:06:19 |
-O1 | 00:03:01 |
-O2 | 00:03:00 |
-O3 | 00:02:59 |
-O2 -march=i686 | 00:02:57 |
-O3 -march=i686 | 00:02:55 |
-O3 -march=p4 only | 00:02:56 |
-O3 -march=p4 sse funrall | 00:03:26 |
-O3 -march=p3 sse funrall | 00:02:56 |
-O3 -march=p3 sse w/o funrall | 00:02:56 |
Увы — картина все та же (рис. 2): резкое падение быстродействия при -O0
, ощутимое — при -march=pentium4
со всеми sse-прибамбасами, прочие же случаи — практически равны…
спасибо! пойду пересобирать lame