Тестирование Linux’ов. Осень 2003. Тур 3. Парадоксы оптимизации


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 сотоварищи на практике оказываются изрядно задумчивыми).

Рис. 1.

Конечно, интересно было бы посмотреть, имеет ли место такой эффект при экстремальной оптимизации под 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

Увы — картина все та же (рис. 2): резкое падение быстродействия при -O0, ощутимое — при -march=pentium4 со всеми sse-прибамбасами, прочие же случаи — практически равны…

Тестирование Linux’ов. Осень 2003. Тур 3. Парадоксы оптимизации: 1 комментарий

Обсуждение закрыто.