Как стать автором
Обновить
0

Когда размер имеет значение

Время на прочтение 6 мин
Количество просмотров 17K


Иногда встречаются задачи, для которых уменьшение размера приложения, а точнее, правильный баланс между размером и производительностью, является даже более приоритетным, чем скорость его выполнения. Такого рода проблемы существуют, в частности, при разработке для встраиваемых (embedded) систем. Для них приложения «затачиваются» под конкретный тип процессора с очень ограниченным размером памяти, а значит размер нашего приложения будет напрямую влиять на стоимость конечного продукта. Кроме того, можно добавить больше функциональности и улучшить качество самого кода.

Компиляторы Intel обычно отдают предпочтение производительности и слабо заботятся о размере получаемого на выходе приложения. По умолчанию, фокус на максимальную скорость. Задача разработчика заключается в умении найти правильный баланс между скоростью выполнения приложения и используемыми оптимизациями компилятора, и его размером. В компиляторе Intel C/C++ имеется целый ряд возможностей, позволяющий контролировать этот баланс и делать размер приложения более приоритетным, чем его производительность. Давайте рассмотрим эти возможности.

Меньше – лучше!
В компиляторе имеется специальная опция -Os, с помощью которой можно включить оптимизации, создающие меньший размер кода по сравнению с O2 по умолчанию. Кроме того, можно понизить уровень оптимизации до O1, что повлечет за собой отключение векторизации и целого ряда других оптимизаций, но заметно сократит размер. Тем не менее, лучше отключать оптимизации пошагово, а не сразу все.

Опции: -Os (Linux/OS X) и /Os (Windows)
Visual Studio: Optimization > Favor Size or Speed
+: уменьшение размера кода по сравнению с опцией O2
-: незначительная потеря производительности

Не используем – «в топку»
По умолчанию линковщик работает на уровне COMDAT секций. При компиляции весь код сохраняется в одиночной секции объектного файла — цельном блоке, который линкер не имеет права разрезать. В результате он не может удалять неиспользуемые функции и глобальные переменные, а нам бы очень этого хотелось.
Но мы можем включить линковку уровня функций, упаковывая функции и переменные в отдельные секции, например, с опцией /Gy на Windows. В этом случае линкер сможет манипулировать с ними по отдельности и с помощью ключа линковщика (/OPT:REF) выкинет все сущности, на которые вообще никто не ссылается (то есть строится граф зависимостей, и всё, что не попадает в этот граф — выкидывается). Таким образом можно значительно уменьшить размер приложения.

Опции: -fdata-sections, -ffunction-sections, -WI,--gc-sections (Linux/OS X) и /Gy /Qoption,link,/OPT:REF (Windows)
+: оставляем только используемый при исполнении код
-: необходима поддержка линковщика и возможно увеличение времени линковки

Отключаем инлайнинг интринсик-функций
Обычно код, в котором используются интринсики быстрее, потому что нет накладных расходов на вызовы функций. Поэтому компилятор любит заменять вызовы некоторых функций на интринсики, генерируя больше кода в угоду производительности. Например, аналоги есть у функций abs, strcpy, sin и так далее. Можно отключить инлайнинг для одной или нескольких таких интринсик-функций.

Опции: -fno-builtin[-name](Linux/OS X) и /Oi- (/Qno-builtin-name) (Windows)
Visual Studio: Optimization > Enable Intrinsic Functions (/Oi)
+: уменьшение кода объектных файлов
-: могут быть отключены другие оптимизации; возможно использование более медленных библиотечных функций

Линкуем библиотеки Intel динамически
Мы можем отменить статическую линковку библиотек Intel (-static-intel), увеличивающую размер, на Linux. Если делать это на OS X, то придётся ещё выставить переменную DYLD_LIBRARY_PATH. Опция -shared-intel так же включается с -mcmodel=medium или -mcmodel=large, контролирующими модель работы компилятора с памятью.

Опции: -shared-intel(Linux/OS X)
Xcode: Runtime > Intel Runtime Libraries
+: нет влияния на производительность; все библиотеки доступны для использования
-: придется поставлять библиотеки вместе с приложением

Используем межпроцедурную оптимизацию
Редкий случай, когда нам нужно включить оптимизацию, чтобы уменьшить размер приложения. Оптимизация IPO позволяет уменьшать размер кода за счет того, что код не генерируется для функций, которые всегда инлайнятся или никогда не вызываются, а также удаления мёртвого кода.
Опции: -ipo(Linux/OS X) и /Qipo (Windows)
Visual Studio: Optimization > Interprocedural Optimization
Eclipse: Optimization > Enable Whole Program Optimization

+: улучшает производительность и уменьшает размер исполняемого файла
-: размер двоичных файлов может увеличиться; не рекомендуется для случаев, когда финальным продуктом являются объектные файлы

Отключаем передачу аргументов через регистры
У компилятора имеется оптимизация, позволяющая передавать аргументы через регистры, а не стек. Её можно отключить, избегая создания дополнительной точки входа, увеличивающей размер. Опция доступна только на 32 битной архитектуре.

Опции: -opt-args-in-regs=none(Linux/OS X) и /Qopt-args-in-regs:none (Windows)
+: возможно уменьшение размера кода
-: уменьшение кода может быть значительно меньше относительно потери производительности

Отключаем инлайнинг
Инлайнинг улучшает производительность за счет отсутствия вызова функций, но увеличивает размер. Альтернативой полному отключению инлайнинга является использование специального коэффициента, позволяющего более тонко контролировать его использование.

Опции: -fno-inline(Linux/OS X) и /Ob0 (Windows)
/Qinline-factor=n ( 0 <= n < 100)
Visual Studio: Optimization > Inline Function Expansion
Eclipse: Optimization > Inline Function Expansion
Xcode: Optimization > Inline Function Expansion

+: уменьшение размера кода
-: уменьшение производительности

Работа с исключениями
Не стоит забывать, что компилятор создает специальный код для обработки исключений, что, естественно, увеличивает размер приложения из-за большого размера EH (exception handling) секции. Опция -fno-exceptions позволяет отключить генерацию таблиц обработки исключений, при этом её нельзя использовать для приложений, генерирующих исключения. В случае, если мы собрали код с этой опцией, любое использование обработки исключений, например, try блока, будет выдавать ошибку.
Так же имеется опция -fno-asynchronous-unwind-tables, позволяющая отключить создание таблиц раскрутки для следующих функций:
— С++ функции, которые не создают объекты с деструкторами и не вызывают другие функции, способные генерировать исключения
— С/С++ функции, скомпилированные без -fexceptions и, в случае архитектуры Intel® 64, без опции -traceback
— С/С++ функции, скомпилированный с -fexceptions, не содержащие вызовов других функций, способных генерировать исключения

Опции: -fno-exceptions, -fno-asynchronous-unwind-tables (Linux/OS X)
+: уменьшение размера бинарника до 15%
-: возможно изменение поведения приложения

Не используем библиотеки
Можно сказать компилятору, чтобы он придерживался правил по работе в автономном окружении (Freestanding Environment), при котором он не использует стандартные библиотеки.Таким образом, компилятор будет генерировать вызовы только тех функций, которые есть в коде. Пример подобного приложения – ядро ОС.

Опции: -ffreestanding, -gnudefaultlibs (Linux/OS X) и /Qfreestanding (Windows)
+: уменьшение размера двоичного файла до 15%
-: возможна потеря в производительности, если код из библиотек активно использовался

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

Опции: --WI,--strip-all (Linux/OS X)
+: существенное уменьшение размера
-: отладка приложения практически невозможна без информации о символах

Отключаем векторизацию
Можно пойти на полное или частичное (через директивы) отключение векторизации.

Опции: -no-vec (Linux/OS X) и /Qvec- (Windows)
+: существенное уменьшение времени компиляции, меньший размер
-: производительность существенно просядет. Возможно отключение векторизации для некоторых циклов, которые не критичны для производительности, с помощью директивы #pragma novector

Ненужное выравнивание по 16 байт
На 32-битной архитектуре компилятор выполняет 16-байтное выравнивание, что может создавать дополнительные инструкции для выравнивания стэка при вызове функции. В случае, если в коде много мелких функций, размер может быть сильно увеличен. Используйте опцию, если
— в коде нет вызовов других библиотечных функций, построенных без этой опции
— код заточен под архитектуры, которые не поддерживают SSE инструкции и не требуют выравнивания для корректности результатов
Опции: -falign-stack=assume-4-byte (Linux/OS X 32 bit)
+: уменьшение размера кода из-за отсутствия дополнительных инструкций; производительность так же может быть увеличена из-за уменьшения числа инструкций
-: несовместимость при линковке с другими библиотеками

Отключаем развёртку циклов (loop unrolling)
Развертка циклов может увеличивать размер пропорционально коэффициенту развёртки (unroll factor). Отключение или ограничение этой оптимизации позволит уменьшить размер за счет потери производительности. Возможно контролировать развёртку с помощью директивы #pragma unroll. Опция включается по дефолту с -Os/-O1.

Опции: -unroll=0 (Linux/OS X) и /Qunroll:0 (Windows)
+: уменьшение размера кода; возможность контролировать развёртку для отдельных циклов
-: производительность может заметно просесть, так как другие оптимизации цикла могут быть тоже ограничены

В заключении отмечу, что используя перечисленные опции шаг за шагом можно постепенно уменьшать размер приложения, так или иначе жертвуя производительностью. Главное, найти правильный баланс!
Теги:
Хабы:
+22
Комментарии 7
Комментарии Комментарии 7

Публикации

Информация

Сайт
www.intel.ru
Дата регистрации
Дата основания
Численность
5 001–10 000 человек
Местоположение
США
Представитель
Анастасия Казантаева

Истории