Вопрос или проблема
Говорят, что компиляция инструментов GNU и ядра Linux с опцией оптимизации gcc -O3
приводит к странным и забавным ошибкам. Это правда? Кто-нибудь пробовал, или это просто выдумка?
Обновление: В наши дни опасения по поводу -O3
, как правило, не обоснованы. -O3
не является опасным или нестабильным и не вносит ошибок в корректный код, однако, как и с любым уровнем оптимизации, неопределенное поведение может проявляться, когда компилятор использует оптимизации -O3
. -O3
иногда может создавать более медленный код, например, из-за различных решений о том, когда использовать cmov
вместо ветвления и подобных различий, но это также верно практически для любого флага оптимизации. Лучше всего провести тестирование и профилирование. В случае ядра Linux, оказывает ли -O3
какое-либо влияние вообще, сильно зависит от рабочей нагрузки и, вероятно, не должно иметь значения, так как ядро не занимается тяжелыми вычислениями.
-O3
имеет несколько недостатков:
- Прежде всего, он часто производит более медленный код, чем
-O2
или-Os
. Иногда он создает более длинный код из-за разворачивания циклов, что, по сути, может быть медленнее из-за ухудшенного кэширования кода. - Как было сказано, он иногда производит неправильный код. Это может быть либо из-за ошибки в оптимизации, либо из-за ошибки в коде (например, игнорирования строгой алиасности). Так как код ядра иногда бывает «умным», я бы сказал, что возможно, что какой-то разработчик ядра допустил ошибку. Я сталкивался с различными странными проблемами, такими как аварийный останов утилит пользовательского пространства, когда компилировал ядро с gcc 4.5, который на тот момент считался стабильным. Я все еще использую gcc 4.4 для ядра и некоторых выбранных утилит пользовательского пространства из-за различных ошибок. То же самое может применяться и к
-O3
. - Я не думаю, что он приносит много пользы для ядра Linux. Ядро не занимается тяжелыми вычислениями, и в тех местах, где это происходит, оно оптимизировано на уровне сборки. Флаг
-O3
не изменит стоимость переключения контекста или скорость ввода/вывода. Я не думаю, что что-то вроде <0,1% ускорения общей производительности того стоит.
За последние 10 лет я использовал несколько систем Gentoo с более чем 1000 пакетами, используя глобально -O3 -march=native
, и еще не сталкивался с какой-либо из мифических проблем стабильности, которые, как предполагается, есть у -O3
. Тесты производительности приложений, требующих больших ресурсов процессора (таких как математические/научные приложения), постоянно показывают, что -O3
производит более быстрый код, в конце концов, это было бы бессмысленно, если бы это было не так. Для большинства настольных приложений CFLAGS
не имеет большого значения, так как они связаны с вводом/выводом, но это имеет большое значение для серверной части, которая требует больших ресурсов процессора.
Это используется в Gentoo, и я не заметил ничего необычного.
Учтите, что большие части инструментария (в частности, glibc) вообще не компилируются, если вы измените уровни оптимизации. Система сборки настроена на игнорирование ваших предпочтений -O для этих секций в большинстве здравомыслящих дистрибутивов.
Проще говоря, некоторые фундаментальные функции библиотеки и ОС зависят от того, чтобы код действительно делал то, что заявлено, а не то, что было бы быстрее во многих случаях. -fgcse-after-reload в частности (включен в -O3) может вызывать странные проблемы.
-O3 использует некоторые агрессивные оптимизации, которые безопасны, только если определенные предположения об использовании регистров, взаимодействии со стековыми фреймами и повторной входимости функций являются истинными, и эти предположения не гарантированы в некоторых кодах, таких как ядро, особенно когда используется встроенная сборка (как это бывает в некоторых очень низкоуровневых частях ядра и его модулях драйверов).
Да, это старая тема, но на самом деле никто не ответил на вопрос: “Вызывает ли это (опция -O3) действительно ошибки в реальных приложениях? Случалось ли это когда-либо?”
Конечно, я полагаюсь на свой собственный опыт, потому что просто нет другого способа. Я говорю о gcc, начиная с версии 4.4.
Существуют 2 существующих “мифа” о уровне O3 в gcc, но что действительно странно, эти мифы можно найти даже в официальных заявлениях, опубликованных действительно крупными программными компаниями (я не буду перечислять их здесь по понятным причинам).
Миф#1: O3 производит больший код, поэтому он не может поместиться в память кэша, и, следовательно, будет работать медленнее -> на самом деле это O2, которое генерирует больший исполняемый код без значительных преимуществ -> обычно он медленнее, чем O1 – но O3 всегда быстрее, обычно на 20% и более, в основном благодаря векторизации.
Миф#2: O3 нарушает исполняемый код:
Не совсем миф: O3 может повредить код, который не был написан с учетом “O3”. Или, другими словами: O3 повредит программы, которые никогда не предназначались для оптимизации на уровне O3 с помощью gcc. Это обычно требует длительного объяснения, но я постараюсь сделать это как можно короче:
-
O3 всегда генерирует правильный код, но проблема в том, что оптимизированный код быстрее – это сломает программы с неразрешенными/никогда не протестированными логическими гонками (особенно многопоточные приложения, ядро Linux может оказаться идеальной жертвой в этом случае)
-
Уровни оптимизации O2/O3 имеют “умную” функцию для устранения “явно” мертвого кода (“явно” никогда не выполняемого ИЛИ кода, который предоставляет результаты, которые “явно” никогда не используются) – это может вызвать поразительные сбои в программах, которые в противном случае работают безупречно на уровне оптимизации O1.
“действительно ли это вызывает ошибки в реальной программе(ах)?”
Да, это бывает:
Мне приходилось добавлять атрибут функции: attribute((optimize(“O1”))) в некоторых случаях, чтобы предотвратить “глупое” удаление вложенных вызовов, используя уровень оптимизации O3 в gcc.
С уважением
Хотя вы можете обойтись использованием -O3
и других настроек оптимизации для большинства приложений (и это может привести к улучшению скорости), я бы не стал использовать такие настройки на самом ядре или на инструментальной цепочке, необходимой для его сборки (компилятор, binutils и т.д.).
Подумайте об этом: стоит ли 5% прироста производительности подсистем raid и ext3 системных сбоев или потенциальной потери и/или повреждения данных?
Настройте все параметры, которые хотите, для Quake порта, в который вы играете, или для аудио/видео кодеков, которые вы используете для записи ваших DVD коллекций в файлы divx. Вы, вероятно, увидите улучшение. Просто не трогайте ядро, если у вас нет времени на пустую трату и данных, которые вы можете позволить себе потерять.
Я предоставил свой опыт здесь для справки.
Я использую GCC7.2.0 в своем проекте, есть сторонний SDK (многопоточность), построенный с использованием -O2
, C-программа (многопоточность) построена по умолчанию с -O3
и связана с SDK.
При нагрузочном тестировании я обнаружил, что C-программа вызывает Segmentation Fault
в 1 из сотен запусков. Ядро дампа указывало на одну функцию в SDK.
Я несколько раз повторял нагрузочные тесты и всегда сталкивался с Segmentation Fault
, и получал одинаковый дамп ядра.
Затем я собрал C-программу с -O2
и связал с SDK. При тысячах запусков не было обнаружено ни одной ошибки.
Я просто опубликовал свой собственный вопрос, спрашивающий о подтверждении, может ли -O3
действительно генерировать ошибки для многопоточных пользовательских приложений.
Основываясь на комментариях и ответах на этот вопрос, я думаю, ответ должен быть Да
.
Ответ или решение
Компиляция GNU/Linux с оптимизацией -O3 может показаться привлекательной для повышения производительности, однако такая настройка сопровождается рядом предостережений. Вопрос о том, вызывает ли этот уровень оптимизации странные и нестабильные ошибки, вызывается многими факторами. Здесь следует рассмотреть как мифы, так и реальные факты, подкрепленные опытом сообщества и специалистами в области информационных технологий.
Истина и мифы об оптимизации -O3
-
Частота появления ошибок и нестабильности: Одним из распространенных заблуждений является мнение о том, что -O3 всегда порождает неустойчивый или неправильный код. На самом деле, подобные ошибки чаще всего возникают не из-за самого компилятора, а из-за нестандартного написания кода, который не предназначен для агрессивной оптимизации. Если код содержит неопределенное поведение, агрессивные оптимизации могут его проявить.
-
Увеличение размера и производительности: Часто утверждается, что -O3 увеличивает размер кода, что может отрицательно сказываться на производительности из-за ухудшения работы с кешем процессора. С другой стороны, существуют случаи, когда -O3 действительно ускоряет вычислительные задачи, особенно с использованием векторизации.
-
Потенциальные проблемы с совместимостью: Некоторое программное обеспечение и компоненты, такие как glibc, не могут компилироваться при изменении уровня оптимизации ввиду особенностей их внутренней архитектуры и требований к платформам. Эти участки чаще всего игнорируют -O3 для обеспечения надежности и работоспособности.
Практические рекомендации
-
Тестирование и профилирование: Перед применением -O3 на реальных проектах рекомендуется провести тщательное тестирование и профилирование, чтобы понять влияние оптимизации на производительность и стабильность кода.
-
Использование атрибутов оптимизации: В случаях, когда -O3 не подходит для определённых частей кода, возможно использование атрибутов функции для снижения оптимизационного уровня в выбранных секциях, гарантируя тем самым корректное функционирование программы.
-
Контекст приложения: Для приложений, ориентированных на вычислительные задачи и сервера, -O3 может предложить измеримый прирост производительности. Однако для ядра Linux и других критичных системных компонентов важнее стабильность, а не максимальное быстродействие.
Заключая, хотя -O3 может являться мощным инструментом для оптимизации вычислительного кода, его применение должно сопровождаться осторожностью и глубоким пониманием того, как такая оптимизация может повлиять на ваш конкретный проект в контексте архитектуры и поставленных задач.