Какие методы рефакторинга уменьшают размер скомпилированного кода?

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

Ответ 1

  • По возможности используйте функции генерации вместо таблиц данных
  • Отключить встроенные функции
  • Поворот часто используемых макросов в функции
  • Уменьшить разрешение для переменных, больших, чем собственный размер машины (т.е. 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.

Ответ 2

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

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


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

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

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

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


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

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


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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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


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

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

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

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

  • Автоматическая вставка для определенных функций произойдет → увеличит размер кода
  • Консолидация программного обеспечения включена → увеличит размер кода

Что такое конвейерная обработка программного обеспечения?

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

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


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

Ответ 7

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

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];
....
}