Подтвердить что ты не робот

Оптимизация g++ за пределами -O3/-Ofast

Проблема

У нас есть программа среднего размера для задачи моделирования, которую мы должны оптимизировать. Мы уже сделали все возможное, чтобы оптимизировать источник до предела наших навыков программирования, включая профилирование с помощью Gprof и Valgrind.

Когда мы закончим, мы хотим запустить программу на нескольких системах, возможно, в течение нескольких месяцев. Поэтому мы действительно заинтересованы в том, чтобы оптимизировать ограничения.

Все системы будут запускать Debian/Linux на относительно новом оборудовании (Intel i5 или i7).

Вопрос

Каковы возможные варианты оптимизации с использованием последней версии g++, выходящей за пределы -O3/-Ofast?

Мы также заинтересованы в дорогостоящей незначительной оптимизации, которая будет выплачиваться в долгосрочной перспективе.

Что мы используем прямо сейчас

В настоящее время мы используем следующие варианты оптимизации g++:

  • -Ofast: Самый высокий уровень "стандартного" уровня оптимизации. Включенный -ffast-math не вызывал проблем в наших расчетах, поэтому мы решили пойти на это, несмотря на нестандартное соответствие.
  • -march=native: включение всех инструкций, относящихся к ЦП.
  • -flto, чтобы обеспечить оптимизацию времени соединения, в разных единицах компиляции.
4b9b3361

Ответ 1

Большинство ответов предлагают альтернативные решения, такие как разные компиляторы или внешние библиотеки, которые, скорее всего, приведут к большой переработке или интеграции. Я постараюсь придерживаться того, что задает вопрос, и сосредоточиться на том, что можно сделать только с помощью GCC, путем активации флагов компилятора или внесения минимальных изменений в код, по просьбе OP. Это не ответ "вы должны сделать", а больше набор настроек GCC, которые хорошо сработали для меня, и что вы можете попробовать, если они релевантны в вашем конкретном контексте.


Предупреждения относительно исходного вопроса

Прежде чем вдаваться в подробности, несколько предупреждений относительно вопроса, как правило, для людей, которые придут, прочитают вопрос и скажут: "ОП оптимизируется за пределами O3, я должен использовать те же флаги, что и он!".

  • -march=native позволяет использовать инструкции, специфичные для данной архитектуры процессора, и которые необязательно доступны в другой архитектуре. Программа может вообще не работать, если работать в системе с другим процессором или быть значительно медленнее (так как это также позволяет mtune=native), поэтому имейте это в виду, если вы решите ее использовать. Подробнее здесь.
  • -Ofast, как вы заявили, позволяет оптимизировать нестандартную совместимость, поэтому его следует использовать с осторожностью. Подробнее здесь.

Другие флаги GCC для тестирования

Детали для разных флагов перечислены здесь.

  • -Ofast позволяет -ffast-math, что, в свою очередь, позволяет -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans и -fcx-limited-range. Вы можете пойти еще дальше на оптимизацию расчета с плавающей запятой, выборочно добавив некоторые дополнительные флаги, такие как -fassociative-math, -freciprocal-math, -fno-signed-zeros и -fno-trapping-math. Они не включены в -Ofast и могут дать некоторые дополнительные увеличения производительности при расчетах, но вы должны проверить, действительно ли они приносят вам пользу, и не нарушают никаких вычислений.
  • GCC также содержит кучу других флагов оптимизации, которые не включены никакими опциями "-O". Они перечислены как "экспериментальные варианты, которые могут привести к нарушению кода", поэтому их следует использовать с осторожностью, а их эффекты проверяются как путем проверки правильности, так и для бенчмаркинга. Тем не менее, я часто использую -frename-registers, этот вариант никогда не вызывал нежелательных результатов для меня и, как правило, дает заметное увеличение производительности (т.е. Можно измерить при бенчмаркинге). Это тип флага, который сильно зависит от вашего процессора. -funroll-loops также иногда дает хорошие результаты, но зависит от вашего фактического кода.

PGO

GCC имеет функции оптимизации профиля. Существует не так много точной документации по GCC, но, тем не менее, ее запуск достаточно прост.

  • сначала скомпилируйте свою программу с помощью -fprofile-generate.
  • пусть запускается программа (время выполнения будет значительно медленнее, так как код также генерирует информацию профиля в файлы .gcda).
  • перекомпилируйте программу с помощью -fprofile-use. Если ваше приложение многопоточно, добавьте флаг -fprofile-correction.

PGO с GCC может дать потрясающие результаты и действительно значительно повысить производительность (я видел увеличение скорости на 15-20% в одном из проектов, над которыми я недавно работал). Очевидно, что проблема заключается в том, чтобы иметь некоторые данные, которые достаточно репрезентативны для выполнения вашего приложения, что не всегда доступно или легко получить.

Параллельный режим GCC

GCC имеет Параллельный режим, который был впервые выпущен примерно в то время, когда компилятор GCC 4.2 отсутствовал.

В принципе, он предоставляет вам параллельные реализации многих алгоритмов в стандартной библиотеке С++. Чтобы включить их во всем мире, вам просто нужно добавить флаги -fopenmp и -D_GLIBCXX_PARALLEL в компилятор. Вы также можете выборочно включать каждый алгоритм, когда это необходимо, но для этого потребуются незначительные изменения кода.

Вся информация об этом параллельном режиме находится здесь здесь.

Если вы часто используете эти алгоритмы в больших структурах данных и имеете много контекстов аппаратного потока, эти параллельные реализации могут дать огромный прирост производительности. Я использовал только параллельную реализацию sort, но для того, чтобы дать приблизительную идею, мне удалось сократить время сортировки с 14 до 4 секунд в одном из моих приложений (тестовая среда: вектор из 100 миллионов объектов с пользовательская функция компаратора и 8-ядерная машина).

Дополнительные трюки

В отличие от предыдущих разделов точек, эта часть требует небольших изменений в коде. Они также специфичны для GCC (некоторые из них также работают над Clang), поэтому макросы макросов компиляции должны использоваться для сохранения кода в других компиляторах. Этот раздел содержит несколько более сложных методов и не должен использоваться, если у вас нет понимания на уровне сборки того, что происходит. Также обратите внимание, что в настоящее время процессоры и компиляторы довольно умны, поэтому может быть сложно получить какую-либо заметную выгоду от описанных здесь функций.

  • GCC встроены, которые перечислены здесь. Конструкции, такие как __builtin_expect, могут помочь компилятору улучшить оптимизацию, предоставив ему информацию прогнозирования ветвей. Другие конструкции, такие как __builtin_prefetch, вносят данные в кеш, прежде чем они будут доступны, и могут помочь уменьшить пропуски кэша.
  • которые перечислены здесь. В частности, вы должны изучить атрибуты hot и cold; первый будет указывать компилятору, что функция является hotspot программы и более агрессивно оптимизирует функцию и помещает ее в специальный подраздел текстового раздела для лучшей локальности; более поздняя версия будет оптимизировать функцию для размера и поместить ее в другой специальный подраздел текстового раздела.

Я надеюсь, что этот ответ окажется полезным для некоторых разработчиков, и я буду рад рассмотреть любые изменения или предложения.

Ответ 2

относительно новое оборудование (Intel i5 или i7)

Почему бы не инвестировать в копию компилятора Intel и высокопроизводительных библиотек? Он может опередить GCC на оптимизацию с существенным отрывом, как правило, от 10% до 30% или даже больше, а тем более для тяжелых программ хеширования. И Intel также предоставляет ряд расширений и библиотек для высокопроизводительных приложений с хрустом (параллельными), если это то, что вы можете позволить себе интегрировать в свой код. Это может привести к большому выигрышу, если это закончится тем, что вы сохраните месяцы работы.

Мы уже сделали все возможное, чтобы оптимизировать источник до предела наших навыков программирования.

По моему опыту, такие микро- и нано-оптимизации, которые вы обычно делаете с помощью профилировщика, как правило, имеют плохую отдачу от инвестиций во времени по сравнению с макро-оптимизациями (упорядочение структуры кода) и, что наиболее важно и часто упускается из виду, оптимизация доступа к памяти (например, локальность ссылки, обход в порядке, минимизация косвенности, устранение промахов кэш-памяти и т.д.). Последнее обычно включает в себя проектирование структур памяти, чтобы лучше отражать способ использования памяти (пройденный). Иногда это может быть так же просто, как переключение типа контейнера и получение огромного повышения производительности. Часто с помощью профилировщиков вы теряетесь в деталях оптимизации инструкций по инструкциям, а проблемы с макетом памяти не отображаются и обычно пропускаются, когда вы забываете смотреть на большую картинку. Это гораздо лучший способ инвестировать свое время, и выигрыши могут быть огромными (например, многие алгоритмы O (logN) в конечном итоге выполняют почти так же медленно, как O (N), только из-за плохих макетов памяти (например, используя связанный список или связанное дерево является типичным виновником огромных проблем с производительностью по сравнению со смежной стратегией хранения)).

Ответ 3

Если вы можете себе это позволить, попробуйте VTune. Это дает МНОГО больше информации, чем простая выборка (предоставляется gprof, насколько мне известно). Вы можете попробовать Code Analyst. Latter - это достойное бесплатное программное обеспечение, но оно может работать неправильно (или вообще) с процессорами Intel.

Будучи оснащен таким инструментом, он позволяет вам проверять различные меры, такие как использование кеша (и в основном макет памяти), который, если использовать его в полной мере, обеспечивает огромный импульс эффективности.

Когда вы уверены, что алгоритмы и структуры оптимальны, вам обязательно нужно использовать несколько ядер на i5 и i7. Другими словами, поиграйте с различными алгоритмами/шаблонами параллельного программирования и посмотрите, сможете ли вы ускорить скорость.

Если у вас действительно параллельные данные (массивные структуры, на которых вы выполняете аналогичные/одинаковые операции), вы должны предоставить OpenCL и инструкции SIMD ( проще настроить) попробовать.

Ответ 4

huh, то последнее, что вы можете попробовать: ACOVEA project: Анализ оптимизаторов компилятора с помощью эволюционного алгоритма - как видно из описание, он пытается генетический алгоритм выбрать наилучшие параметры компилятора для вашего проекта (делать время компиляции maaany и проверять время, давая обратную связь алгоритму:) - но результаты могут быть впечатляющими!:)

Ответ 5

Трудно ответить без дальнейших подробностей:

  • какой тип числа хруст?
  • какие библиотеки вы используете?
  • какая степень паралелизации?

Можете ли вы записать часть своего кода, которая занимает самое длинное? (Как правило, плотная петля)

Если вы связаны с ЦП, ответ будет отличаться от того, если вы привязаны к IO.

Опять же, пожалуйста, предоставьте дополнительную информацию.

Ответ 6

Некоторые заметки о выбранном в данный момент ответе (у меня недостаточно очков репутации, чтобы опубликовать это как комментарий):

В ответ говорится:

-fassociative-math, -freciprocal-math, -fno-signed-zeros и -fno-trapping-math. Они не включены в -Ofast и могут дать некоторые дополнительные повышения производительности при расчетах

Возможно, это было так, когда ответ был отправлен, но документация GCC говорит, что все они включены -funsafe-math-optimizations, что активируется -ffast-math, который активируется -Ofast. Это можно проверить с помощью команды gcc -c -Q -Ofast --help=optimizer, которая показывает, какие оптимизации включены -Ofast, и подтверждает, что все они включены.

В ответе также говорится:

другие флаги оптимизации, которые не включены никакими параметрами "-O"... -frename-registers

Опять же, приведенная выше команда показывает, что, по крайней мере, с моим GCC 5.4.0, -frename-registers включен по умолчанию с -Ofast.

Ответ 7

Я бы порекомендовал взглянуть на тип операций, которые вызывают тяжелый подъем, и искать оптимизированную библиотеку. Существует довольно много быстрых, оптимизированных по сборке, библиотек с SIMD-векторами для общих проблем (в основном, математики). Повторное использование колеса часто заманчиво, но обычно это не стоит усилий, если существующее soltuion может удовлетворить ваши потребности. Поскольку вы не указали, что это за имитация, я могу привести только некоторые примеры.

http://www.yeppp.info/

http://eigen.tuxfamily.org/index.php?title=Main_Page

https://github.com/xianyi/OpenBLAS

Ответ 8

с gcc intel turn/implementation -fno-gcse (хорошо работает на gfortran) и -fno-guess-branch-prbability (по умолчанию в gfortran)