2003 г
Парадоксальные результаты первого тура тестирования побудили меня обратиться к изучению вопроса о том, а насколько же оптимизация реально влияет на производительность? Результаты исследования оказались столь поразительными, что я не могу не поделиться ими на этих страницах. Однако начну по порядку.
Исследования проводились на моей домашней машине - напомню, это P4/2,53 с 533-мегагерцной шиной, мама на i845PE, памяти - 1 Гбайт (2x512, 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-прибамбасами, прочие же случаи - практически равны...