Какие существуют методы рефакторинга для уменьшения размера скомпилированного кода? - PullRequest
13 голосов
/ 19 марта 2009

У меня есть устаревшее приложение для прошивки, которое требует новых функций. Размер приложения уже был близок к ограниченному объему флэш-памяти устройства, и несколько новых функций и переменных переместили его через край. Включение оптимизации компилятора делает свое дело, но заказчик настороженно относится к этому, потому что в прошлом они вызывали сбои. Итак, что нужно искать при рефакторинге кода C для получения меньшего результата?

Ответы [ 7 ]

20 голосов
/ 19 марта 2009
  • При возможности используйте функции генерации вместо таблиц данных
  • Отключить встроенные функции
  • Превратить часто используемые макросы в функции
  • Уменьшить разрешение для переменных, превышающих размер собственной машины (т. Е. 8-битные микро, попробуйте избавиться от 16- и 32-битных переменных - удваивает и удваивает некоторые последовательности кода)
  • Если микро имеет меньший набор команд (большой палец руки), включите его в компиляторе
  • Если память сегментирована (т. Е. Разбита на страницы или нелинейна), тогда
    • Переставьте код, чтобы использовать меньшее количество глобальных вызовов (более крупные инструкции вызова)
    • Изменение порядка использования кода и переменных для устранения вызовов глобальной памяти
    • Пересмотреть глобальное использование памяти - если ее можно разместить в стеке, тем лучше
  • Убедитесь, что вы компилируете с отключенной отладкой - на некоторых процессорах это имеет большое значение
  • Сжатие данных, которые не могут быть сгенерированы на лету, а затем сжатие в оперативную память при запуске для быстрого доступа
  • Изучите параметры компилятора - может случиться так, что каждый вызов автоматически будет глобальным, но вы можете безопасно отключить его для каждого файла, чтобы уменьшить размер (иногда значительно)

Если вам все еще нужно больше места, чем при включенном compile with optimizations, посмотрите на сгенерированную сборку и неоптимизированный код. Затем переписайте код, в котором произошли самые большие изменения, чтобы компилятор генерировал те же оптимизации на основе хитрой перезаписи C с отключенной оптимизацией.

Например, у вас может быть несколько операторов if, которые проводят аналогичные сравнения:

if(A && B && (C || D)){}
if(A && !B && (C || D)){}
if(!A && B && (C || D)){}

Тогда создание новой переменной и предварительное сравнение спасет компилятор от дублирования кода:

E = (C || D);

if(A && B && E){}
if(A && !B && E){}
if(!A && B && E){}

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

8 голосов
/ 19 марта 2009

Обычно: используйте свою карту линкера или инструменты, чтобы выяснить, какие у вас самые большие / самые многочисленные символы, а затем, возможно, посмотрите на них, используя дизассемблер. Вы будете удивлены тем, что вы найдете таким образом.

С помощью небольшого количества perl или чего-то подобного вы можете быстро обработать файл .xMAP или результаты «objdump» или «nm» и заново отсортировать его для получения необходимой информации.


Специфично для небольших наборов команд: следите за использованием буквального пула . При изменении, например, команда ARM (32 бита на команду), установленная на набор команд THUMB (16 бит на команду), может быть полезна на некоторых процессорах ARM, поскольку она уменьшает размер поля «немедленное».

Внезапно что-то, что будет прямой загрузкой от глобальной или статической, становится очень косвенным; сначала он должен загрузить адрес global / static в регистр, а затем загрузить его, а не просто кодировать адрес непосредственно в инструкции. Таким образом, вы получаете несколько дополнительных инструкций и дополнительную запись в пуле литералов для чего-то, что обычно было бы одной инструкцией.

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

Мы преобразовали наши «одноэлементные» классы из управления своими собственными указателями экземпляров в просто членство в большой «struct GlobalTable», и это заметно изменило размер кода (несколько процентов), а также производительность в некоторых случаях.


В противном случае: следите за статическими структурами и массивами нетривиально построенных данных. Каждый из них обычно генерирует огромное количество кода .sinit («невидимые функции», если хотите), которые запускаются перед main () для правильного заполнения этих массивов. Если вы можете использовать в статистике только тривиальные типы данных, вам будет гораздо лучше.

Это опять то, что может быть легко идентифицировано с помощью инструмента по результатам «nm» или «objdump» и т.п. Если у вас есть куча вещей .sinit, вы захотите исследовать!


Да, и - если ваш компилятор / компоновщик поддерживает это, не бойтесь выборочно включать оптимизацию или меньшие наборы команд только для определенных файлов или функций!

2 голосов
/ 19 марта 2009

Рефакторинг дубликата кода должен оказать наибольшее влияние на объем памяти вашей программы.

0 голосов
/ 10 апреля 2019

вы можете сделать много вещей, но эти две вещи очень помогли мне в прошлом Я просто хочу предложить один

1 - не использовать общую стандартную библиотеку C, например sprintf , ... они очень общие и если вы напишите свою собственную функцию, это освободит много места

2-Если у вас есть локальное объявление массива char, если вы знаете максимальную длину, вы должны явно указать эту длину вместо того, чтобы получать ее с помощью входного аргумента, например,

если у вас есть такая функция

void foo(char* str,uint8_t length){
char local_string[length];
....
}

вам лучше найти максимальную длину, которую вы используете, затем измените ее на

void foo(char* str,uint8_t length){
char local_string[MAXIMUM_LENGTH];
....
}
0 голосов
/ 19 марта 2009

В приведенных выше ответах утверждается "Включение оптимизации компилятора [уменьшил размер кода]". Учитывая всю документацию и опыт, которые у меня были во встроенных системах Программирование TI DSP, я точно знаю, что включение оптимизации УВЕЛИЧИТ ваш размер кода (для чипа TI DSP)!


Позвольте мне объяснить:

TI TMSCx6416 DSP имеет 9 флагов компилятора, которые влияют на размер вашего кода.

  1. 3 различных флага для оптимизации
  2. 3 различных флага для отладки
  3. 3 различных флага для размера кода

Для моего компилятора при включении третьего уровня оптимизации документация гласит:

  1. Произойдет автоматическая вставка для определенных функций -> увеличит размер кода
  2. Программная конвейеризация включена -> увеличит размер кода

Что такое программный конвейер?

Именно здесь компилятор будет выполнять в сборке такие вещи, которые заставляют циклы for выполняться значительно быстрее (до пары раз быстрее), но за счет большего размера кода. Я предлагаю прочитать о программной конвейеризации в Википедии (ищите развертывание цикла, пролог и эпилог).

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


Другим предложением является поиск флагов компилятора, которые относятся к размеру кода. Если у вас есть флаги компилятора размера кода, убедитесь, что они установлены на максимальные значения. Обычно компиляция для размера кода означает, что ваш код будет выполняться медленнее ... но вам, возможно, придется это сделать.

0 голосов
/ 19 марта 2009

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

Он предпочитает размер, а не читабельность, поэтому иногда бывает довольно уродливо, с gotos и так далее.

0 голосов
/ 19 марта 2009

Обратите внимание на макросы. Они могут генерировать много кода только из одного макроса. Если вы найдете такие макросы - попробуйте переписать их так, чтобы их размер был минимальным, а функциональность перенесена в функции.

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

Проверьте, поддерживает ли компилятор вставку и можно ли его отключить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...