Я работал над разработкой функции для нашего конкретного продукта. Был запрос на перенос той же функции на другой продукт. Этот продукт основан на микроконтроллере M16C, который традиционно имеет 64K Flash и 2k RAM.
Это зрелый продукт, поэтому у него осталось всего 132 байта флэш-памяти и 2 байта оперативной памяти.
Чтобы портировать запрашиваемую функцию (сама функция была оптимизирована), мне нужно 1400 байт флэш-памяти и ~ 200 байт оперативной памяти.
Кто-нибудь есть какие-либо предложения о том, как получить эти байты путем сжатия кода? Какие конкретные вещи я ищу, когда пытаюсь сжать уже существующий рабочий код?
Любые идеи действительно будут оценены.
Благодарю.
Ответы:
У вас есть несколько вариантов: во-первых, искать избыточный код и перемещать его в один вызов, чтобы избавиться от дублирования; во-вторых, удалить функциональность.
Внимательно посмотрите на свой файл .map и посмотрите, есть ли функции, от которых вы можете избавиться или переписать. Также убедитесь, что используемые библиотечные вызовы действительно необходимы.
Определенные вещи, такие как деление и умножение, могут принести много кода, но использование сдвигов и более эффективное использование констант может сделать код меньше. Также взгляните на такие вещи, как строковые константы и
printf
s. Например, каждый из нихprintf
съест ваш диск, но вы можете иметь пару строк общего формата вместо того, чтобы повторять эту строковую константу снова и снова.Для памяти посмотрите, можете ли вы избавиться от глобальных переменных и использовать вместо них autos в функции. Также избегайте как можно большего количества переменных в основной функции, так как они поглощают память, как это делают глобальные переменные.
источник
Всегда стоит посмотреть вывод списка файлов (ассемблера), чтобы найти вещи, в которых ваш конкретный компилятор особенно плох.
Например, вы можете обнаружить, что локальные переменные очень дороги, и если приложение достаточно простое, чтобы оправдать риск, перемещение нескольких счетчиков циклов в статические переменные может сэкономить много кода.
Или индексация массива может быть очень дорогой, но операции с указателями намного дешевле. Или наоборот.
Но смотреть на ассемблере - это первый шаг.
источник
Оптимизация компилятора, например,
-Os
в GCC дает лучший баланс между скоростью и размером кода. Избегайте-O3
, так как это может увеличить размер кода.источник
Для оперативной памяти проверьте диапазон всех ваших переменных - используете ли вы целочисленные значения, где можно использовать символ? Буферы больше, чем они должны быть?
Сжатие кода очень зависит от приложения и стиля кодирования. Ваши оставшиеся суммы указывают на то, что, возможно, код уже ушел, хотя какое-то сжатие, что может означать, что осталось немного.
Также внимательно посмотрите на общую функциональность - есть ли что-то, что на самом деле не используется и может быть отброшено?
источник
Если это старый проект, но компилятор был разработан с тех пор, возможно, более поздний компилятор может производить меньший код
источник
Всегда стоит обратиться к руководству по компилятору за вариантами оптимизации пространства.
Для GCC
-ffunction-sections
и-fdata-sections
с--gc-sections
флагом компоновщика хороши для удаления мертвого кода.Вот некоторые другие отличные советы (ориентированные на AVR)
источник
Вы можете проверить количество выделенного стекового пространства и пространства кучи. Вы можете получить обратно значительный объем оперативной памяти, если один или оба из них перераспределены.
Я думаю, для проекта , который влезает в 2к ОЗУ , чтобы начать с нет динамического распределения памяти (использование
malloc
,calloc
и т.д.). Если это так, вы можете полностью избавиться от кучи, если исходный автор оставил часть оперативной памяти, выделенную для кучи.Вы должны быть очень осторожны, уменьшая размер стека, так как это может привести к ошибкам, которые очень трудно найти. Может быть полезно начать с инициализации всего пространства стека известным значением (что-то отличное от 0x00 или 0xff, поскольку эти значения обычно уже встречаются), а затем запустить систему на некоторое время, чтобы увидеть, сколько места в стеке не используется.
источник
В вашем коде используется математика с плавающей запятой? Возможно, вам удастся повторно реализовать ваши алгоритмы, используя только целочисленную математику, и устранить издержки использования библиотеки C с плавающей запятой. Например, в некоторых приложениях такие функции, как sine, log, exp можно заменить на целочисленные полиномиальные приближения.
Использует ли ваш код большие справочные таблицы для каких-либо алгоритмов, таких как вычисления CRC? Вы можете попробовать заменить другую версию алгоритма, которая вычисляет значения на лету, вместо использования справочных таблиц. Предостережение заключается в том, что меньший алгоритм, скорее всего, медленнее, поэтому убедитесь, что у вас достаточно циклов ЦП.
Содержит ли ваш код большие объемы постоянных данных, таких как строковые таблицы, HTML-страницы или пиксельная графика (значки)? Если он достаточно большой (скажем, 10 кБ), возможно, стоит реализовать очень простую схему сжатия, чтобы уменьшить объем данных и распаковывать их на лету, когда это необходимо.
источник
Вы можете попытаться изменить код, чтобы сделать его более компактным. Это во многом зависит от того, что делает код. Ключ заключается в том, чтобы найти похожие вещи и повторно реализовать их в терминах друг друга. Экстремальным вариантом было бы использование языка более высокого уровня, такого как Forth, с которым легче достичь более высокой плотности кода, чем в C или ассемблере.
Вот Forth для M16C .
источник
Установите уровень оптимизации компилятора. Многие IDE имеют настройки, которые позволяют оптимизировать размер кода за счет времени компиляции (или, возможно, даже времени обработки в некоторых случаях). Они могут выполнить сжатие кода, перезапустив свой оптимизатор пару раз, выполнив поиск менее распространенных оптимизируемых шаблонов и других приемов, которые могут не понадобиться для случайной / отладочной компиляции. Обычно по умолчанию компиляторы настроены на средний уровень оптимизации. Покопайтесь в настройках и вы сможете найти некоторую целочисленную шкалу оптимизации.
источник
Если вы уже используете компилятор профессионального уровня, такой как IAR, я думаю, что вам будет сложно добиться какой-либо серьезной экономии за счет незначительной низкоуровневой подстройки кода - вам нужно больше смотреть на удаление функциональности или выполнение основных переписывает детали более эффективным способом. Вам нужно быть умнее кодером, чем тот, кто написал оригинальную версию ... Что касается оперативной памяти, вам нужно очень внимательно взглянуть на то, как она используется в настоящее время, и посмотреть, есть ли возможности для перекрытия использования той же оперативной памяти для разные вещи в разное время (для этого пригодятся союзы). Размеры кучи и стека в IAR по умолчанию в ARM / AVR, которые у меня были, как правило, слишком великодушны, так что это будет первое, на что стоит обратить внимание.
источник
Что еще нужно проверить - некоторые компиляторы на некоторых архитектурах копируют константы в ОЗУ - обычно используются, когда доступ к константам флэш-памяти медленный / трудный (например, AVR), например, компилятору IAR AVR требуется квалификатор _ _flash, чтобы не копировать константу в RAM)
источник
Если ваш процессор не имеет аппаратной поддержки для параметра / локального стека, но компилятор все равно пытается реализовать стек параметров времени выполнения, и если ваш код не нуждается в повторном вводе, вы можете сохранить код пространство путем статического распределения авто переменных. В некоторых случаях это должно быть сделано вручную; в других случаях директивы компилятора могут это сделать. Эффективное ручное распределение потребует разделения переменных между подпрограммами. Такое совместное использование должно быть сделано осторожно, чтобы гарантировать, что ни одна процедура не использует переменную, которую другая процедура считает находящейся «в области видимости», но в некоторых случаях преимущества размера кода могут быть значительными.
Некоторые процессоры имеют соглашения о вызовах, которые могут сделать некоторые стили передачи параметров более эффективными, чем другие. Например, на контроллерах PIC18, если подпрограмма принимает один однобайтовый параметр, она может быть передана в регистр; если требуется больше, все параметры должны быть переданы в ОЗУ. Если подпрограмма будет принимать два однобайтовых параметра, может быть наиболее эффективно «передать» один из них в глобальную переменную, а затем передать другой в качестве параметра. Благодаря широко используемым подпрограммам можно сэкономить. Они могут быть особенно значительными, если параметр, передаваемый через global, является однобитовым флагом или если он обычно будет иметь значение 0 или 255 (поскольку существуют специальные инструкции для хранения 0 или 255 в ОЗУ).
В ARM размещение глобальных переменных, которые часто используются вместе, в структуре может значительно уменьшить размер кода и повысить производительность. Если A, B, C, D и E являются отдельными глобальными переменными, то код, который использует все из них, должен загрузить адрес каждого в регистр; если регистров недостаточно, может потребоваться перезагрузить эти адреса несколько раз. Напротив, если они являются частью одной и той же глобальной структуры MyStuff, то код, который использует MyStuff.A, MyStuff.B и т. Д., Может просто один раз загрузить адрес MyStuff. Большая победа.
источник
1. Если ваш код опирается на множество структур, убедитесь, что элементы структуры упорядочены от тех, которые занимают большую часть памяти, до минимума.
Пример: "uint32_t uint16_t uint8_t" вместо "uint16_t uint8_t uint32_t"
Это обеспечит минимальное заполнение структуры.
2. Используйте const для переменных, где это применимо. Это гарантирует, что эти переменные будут в ПЗУ, а не израсходуют ОЗУ.
источник
Несколько (возможно, очевидных) приемов, которые я успешно использовал при сжатии клиентского кода:
Уплотните флаги в битовые поля или битовые маски. Это может быть полезно, так как обычно логические значения хранятся в виде целых чисел, тратя впустую память. Это сэкономит как ОЗУ, так и ПЗУ и обычно не выполняется компилятором.
Ищите избыточность в коде и используйте циклы или функции для выполнения повторных операторов.
Я также сохранил некоторые ПЗУ, заменив многие
if(x==enum_entry) <assignment>
операторы из констант на индексированный массив, позаботившись о том, чтобы записи enum могли использоваться в качестве индекса массива.источник
Если вы можете, используйте встроенные функции или макросы компилятора вместо небольших функций. При передаче аргументов есть размер и скорость, которые можно исправить, сделав функцию встроенной.
источник
int get_a(struct x) {return x.a;}
Измените локальные переменные, чтобы они соответствовали размеру ваших регистров процессора.
Если процессор 32-битный, используйте 32-битные переменные, даже если максимальное значение никогда не превысит 255. Если вы использовали 8-битную переменную, компилятор добавит код для маскировки верхних 24 бит.
Первое, на что я бы обратил внимание - это переменные цикла for.
Это может показаться хорошим местом для 8-битной переменной, но 32-битная переменная может производить меньше кода.
источник