Як видавити код, щоб отримати більше Flash та RAM? [зачинено]


14

Я працював над розробкою функції для певного нашого продукту. Був запит на перевезення тієї ж функції до іншого продукту. Цей продукт базується на мікроконтролері M16C, який традиційно має 64K Flash та 2k оперативної пам’яті.

Це зрілий продукт, а отже, лише 132 байти Flash та 2 байти оперативної пам’яті.

Щоб перенести запитувану функцію (сама функція оптимізована), мені потрібно 1400 байт Flash та ~ 200 байт оперативної пам’яті.

Хтось має пропозиції щодо отримання цих байтів шляхом ущільнення коду? На які конкретні речі я звертаю увагу, коли намагаюся ущільнити вже існуючий робочий код?

Будь-які ідеї дійсно будуть оцінені.

Спасибі.


1
Дякую всім за пропозиції. Я буду постійно інформувати вас про мій прогрес і перелічу кроки, які працювали, і ті, які не робили.
IntelliChick

Добре, ось ось що я спробував, що працював: Переміщено версії компілятора. Оптимізація значно покращилася, що дало мені приблизно 2 Кб Flash. Пройшов файли зі списком, щоб перевірити наявність зайвих та невикористаних функціональних можливостей (успадкованих через загальну базу коду) для конкретного продукту та здобув ще трохи Flash.
IntelliChick

Для оперативної пам’яті я зробив наступне: Пройшов файл карти, щоб перевірити функції / модулі, які використовували найбільшу оперативну пам’ять. Я знайшов дійсно важку функцію (12 каналів, кожен з фіксованою кількістю виділеної пам’яті), застарілого коду, зрозумів, чого намагається досягти, і оптимізував використання оперативної пам’яті, поділившись інформацією між загальними каналами. Це дало мені ~ 200 байт, які мені знадобилися.
IntelliChick

Якщо у вас є файли ascii, ви можете використовувати стиснення від 8 до 7 біт. Економить 12,5%. Використання zip-файлу знадобиться більше коду, щоб зіштовхувати його та скасовувати, ніж просто дозволяти.
Sparky256

Відповіді:


18

У вас є пара варіантів: спочатку потрібно шукати зайвий код і перемістити його в один виклик, щоб позбутися від дублювання; друге - зняти функціональність.

Погляньте на свій файл .map і перевірте, чи є функції, які ви можете позбутися або переписати. Також переконайтесь, що дзвінки до бібліотеки, які використовуються, дійсно потрібні.

Деякі речі, такі як поділ та множення, можуть вносити багато коду, але використання зрушень та кращого використання констант може зменшити код. Також подивіться на такі речі, як рядкові константи та printfs. Наприклад, кожен printfз’їсть ваш ром, але, можливо, ви зможете мати пару рядків спільного формату, а не повторювати цю константну константу знову і знову.

Для пам’яті подивіться, чи можете ви позбутися глобалістів і замість цього використовувати авто в функції. Також уникайте якомога більшої кількості змінних в основній функції, оскільки вони з'їдають пам'ять так само, як і глобальні.


1
Дякую за запропоновані пропозиції, я напевно можу спробувати більшість із них, крім тієї для струнних констант. Це суто вбудований пристрій, без користувальницького інтерфейсу, тому в коді немає дзвінків до printf (). Сподіваючись, що ці пропозиції повинні мене прислухати, щоб отримати мій флеш-пам’ять на 1400 байт / 200 байт оперативної пам'яті, який мені потрібен.
IntelliChick

1
@IntelliChick ви були б вражені тим, як багато людей використовують printf () всередині вбудованого пристрою для друку або для налагодження, або для відправки на периферію. Здається, ви знаєте краще, ніж це, але якщо хтось написав код на проект перед вами, не завадить перевірити це.
Kellenjb

5
І просто для розширення мого попереднього коментаря, ви також були б вражені тим, скільки людей додають заяви про налагодження, але ніколи не видаляйте їх. Навіть люди, які роблять #ifdefs, все одно ледають часом.
Kellenjb

1
Дивовижний, спасибі! Я успадкував цю кодову базу, тому хворі, безумовно, шукаю їх. Я буду тримати вас у курсі, про хід роботи, і намагатимуся відслідковувати, скільки байтів пам'яті або Flash я набрав, роблячи те, що, як орієнтир для всіх, кому це може знадобитися в майбутньому.
IntelliChick

Питання щодо цього - як щодо вкладеної функції виклику переходів від шару до шару. Скільки накладних витрат це додає? Чи краще зберегти модульність, виконавши кілька викликів функцій або зменшити виклики функцій, і зберегти кілька байтів. І це суттєво?
IntelliChick

8

Завжди варто переглядати вихідний файл (асемблер), щоб шукати речі, на які особливо ваши компілятори.

Наприклад, ви можете виявити, що локальні змінні дуже дорогі, і якщо додаток досить простий, щоб бути вартим ризику, переміщення декількох лічильників циклу в статичні змінні може заощадити багато коду.

Або індексація масиву може бути дуже дорогою, але операції з покажчиками значно дешевші. Або навпаки.

Але перегляд мови складання - це перший крок.


3
Дуже важливо, щоб ви знали, що робить ваш компілятор. Ви повинні побачити, що розділяється на моєму компіляторі. Це змушує немовлят плакати (я включаю в себе).
Кортук

8

Оптимізація компілятора, наприклад, -Osу GCC дає найкращий баланс між швидкістю та розміром коду. Уникайте -O3, оскільки це може збільшити розмір коду.


3
Якщо ви це зробите, вам потрібно буде повторно перевірити ВСЕ! Оптимізація може призвести до того, що робочий код не працює через нові припущення, які робить компілятор.
Роберт

@Robert, це справедливо лише в тому випадку, якщо ви використовуєте невизначені оператори: наприклад, a = a ++ буде компілюватися по-різному в -O0 і -O3.
Томас О

5
@Тома неправда. Якщо у вас є цикл для затримки циклів годин, багато оптимізаторів зрозуміють, що ви нічого не робите в ньому, і видалять його. Це лише 1 приклад.
Kellenjb

1
@thomas O, Вам також потрібно переконатися, що ви уважно ставитеся до визначень летючої функції. Оптимізатори підірвуть тих, хто вважає, що добре знає С, але не розуміє складності атомних операцій.
Кортук

1
Всі хороші бали. Летючі функції / змінні, за визначенням, НЕ повинні бути оптимізовані. Будь-який оптимізатор, який здійснює оптимізацію щодо таких (включаючи час виклику та вбудовану трансляцію), порушується.
Томас О

8

Для оперативної пам’яті перевірте діапазон усіх змінних - ви використовуєте вставки, де ви могли б використовувати знак? Чи буфери більше, ніж потрібно?

Видавлювання коду дуже залежить від застосування та кодування. Залишені вами суми говорять про те, що, можливо, код уже пішов, хоча певне стискання, що може означати, що залишилося мало.

Також уважно погляньте на загальну функціональність - чи є щось, що насправді не використовується і яке може бути вимкнено?


8

Якщо це старий проект, але компілятор був розроблений з тих пір, можливо, більш пізній компілятор може створити менший код


Дякую Майку! Я пробував це раніше, і отриманий простір вже використано! :) Перехід від компілятора IAR C 3.21d до 3.40.
IntelliChick

1
Я перейшов на ще одну версію, і мені вдалося отримати ще один Flash для підключення функції. Я дійсно боюся з оперативною пам’яттю, хоча це залишилося незмінним. :(
IntelliChick

7

Завжди варто перевірити у посібнику з компілятора параметри для оптимізації простору.

Для МКІ -ffunction-sectionsі -fdata-sectionsз --gc-sectionsпрапором компоновщика гарні для зняття мертвого коду.

Ось кілька чудових порад (орієнтованих на AVR)


Це насправді працює? Документи кажуть: "Коли ви вкажете ці параметри, асемблер і посилання створюють більші об'єктні та виконувані файли, а також будуть повільнішими". Я розумію, що наявність окремих розділів має сенс для мікросекцій із розділами Flash та RAM. Чи це твердження в документах не стосується мікроконтролерів?
Кевін Вермер

Мій досвід полягає в тому, що він працює добре для AVR
Toby Jaffey

1
Це не працює добре у більшості компіляторів, які я використовував. Це як використання ключового слова регістр. Ви можете сказати компілятору, що змінна потрапляє в реєстр, але хороший оптимізатор зробить це набагато краще, ніж людина (аргументуйте це, як деякі можуть, це вважається неприйнятним на практиці).
Кортук

1
Коли ви починаєте призначати місця, ви змушуєте компілятор розміщувати речі в певних місцях, що дуже важливо для розширеного коду завантажувача, але жахливо для роботи в оптимізаторі, коли ви приймаєте рішення для нього, ви віднімаєте крок оптимізації. міг зробити. У деяких компіляторах вони проектують його, щоб мати розділи для того, для чого використовується код. Цей випадок повідомляє компілятору більше інформації, щоб зрозуміти ваше використання, це допоможе. Якщо компілятор цього не пропонує, не робіть цього.
Кортук

6

Ви можете вивчити кількість виділеного простору та кучу простору. Можливо, ви зможете отримати значну кількість оперативної пам’яті, якщо один або обидва з них перерозподілені.

Я думаю, для проекту , який влазить в 2к ОЗУ для початку немає динамічного розподілу пам'яті (використання malloc, callocі т.д.). Якщо це так, ви можете позбутися від вашої купи взагалі, припускаючи, що оригінальний автор залишив трохи оперативної пам’яті, виділеної для купи.

Ви повинні бути дуже обережними, зменшуючи розмір стека, оскільки це може спричинити помилки, які дуже важко знайти. Може бути корисним для початку, ініціалізуючи весь простір стека до відомого значення (щось інше, ніж 0x00 або 0xff, оскільки ці значення зазвичай трапляються), потім запустити систему деякий час, щоб побачити, скільки місця в стеці не використовується.


Це дуже хороший вибір. Зауважу, ви ніколи не повинні використовувати malloc у вбудованій системі.
Кортук

1
@Kortuk Це залежить від вашого визначення вбудованого та виконуваного завдання
Toby Jaffey

1
@joby, так, я розумію це. У системі з 0 перезапусками та відсутністю такої ОС, як Linux, Malloc може бути дуже поганим.
Кортук

Немає динамічного розподілу пам'яті, немає місця, де використовується malloc, calloc. Я також перевірив розподіл купи, і він уже встановлений на 0, тому немає розподілу купи. В даний час розміщений розмір стека становить 254 байти, а розмір стека переривань - 128 байт.
IntelliChick

5

Чи використовує ваш код математику з плаваючою комою? Можливо, ви зможете повторно реалізувати свої алгоритми, використовуючи лише цілу математику, і усунути накладні витрати на використання бібліотеки з плаваючою точкою C. Наприклад, у деяких програмах такі функції, як sine, log, exp можуть бути замінені цілими поліноміальними наближеннями.

Чи використовує ваш код великі таблиці пошуку для будь-яких алгоритмів, таких як обчислення CRC? Ви можете спробувати замінити іншу версію алгоритму, яка обчислює значення на ходу, замість того, щоб використовувати таблиці перегляду. Застереження полягає в тому, що менший алгоритм, швидше за все, повільніше, тому переконайтеся, що у вас достатньо циклів процесора.

Чи є у вашому коді велика кількість постійних даних, таких як рядкові таблиці, HTML-сторінки або піксельна графіка (піктограми)? Якщо вона достатньо велика (скажімо, 10 кБ), можливо, варто застосувати дуже просту схему стиснення, щоб зменшити дані та декомпресувати її в ході, коли це потрібно.


Є 2 невеликих таблиці пошуку, жодна з яких, на жаль, не складе 10K. І математика з плаваючою точкою також не використовується. :( Дякую за пропозиції. Вони хороші.
IntelliChick

2

Можна спробувати перевпорядкувати, щоб кодувати багато, на більш компактний стиль. Багато що залежить від того, що робить код. Ключ полягає в тому, щоб знайти подібні речі та повторно реалізувати їх з точки зору один одного. Крайнім буде використання мови вищого рівня, як, наприклад, Forth, за допомогою якої можна легше досягти більш високої щільності коду, ніж у C або асемблері.

Ось Forth для M16C .


2

Встановіть рівень оптимізації компілятора. Багато IDE мають налаштування, які дозволяють проводити оптимізацію розміру коду за рахунок часу компіляції (або, можливо, навіть час обробки в деяких випадках). Вони можуть виконати ущільнення коду, повторно перезапустивши свого оптимізатора, шукаючи менш поширені шаблони для оптимізації та цілу низку хитрощів, які можуть не знадобитися для випадкової компіляції / налагодження. Зазвичай за замовчуванням компілятори встановлюються на середній рівень оптимізації. Переконайтесь у налаштуваннях, щоб ви мали змогу знайти якусь шкалу оптимізації на основі цілих чисел.


1
В даний час оптимізовано до максимального розміру. :) Дякую за пропозицію, хоча. :)
IntelliChick

2

Якщо ви вже використовуєте компілятор професійного рівня, як IAR, я думаю, що ви будете намагатися отримати будь-які серйозні заощадження за допомогою незначного налаштування коду низького рівня - вам потрібно буде більше шукати питання щодо видалення функціональності або виконання основних переписує деталі більш ефективно. Вам знадобиться бути розумнішим кодером, ніж той, хто написав оригінальну версію ... Що стосується оперативної пам'яті, вам потрібно дуже уважно поставитися до того, як вона використовується в даний час, і побачити, чи є можливість для накладення використання тієї ж ОЗУ для різні речі в різний час (профспілки зручні для цього). Розміри купівлі та стека IAR за замовчуванням у ARM / AVR, які я, як правило, надмірно щедрі, тому це було б першим, що слід подивитися.


Спасибі Майку. У коді вже використовуються союзи в більшості місць, але я перегляну деякі інші місця, де це все ще може допомогти. Я також ознайомлюся з обраним розміром стека і побачу, чи це взагалі можна оптимізувати.
IntelliChick

Як дізнатися, який розмір стека підходить?
IntelliChick

2

Щось ще потрібно перевірити - деякі компілятори в деяких архітектурах копіюють константи в оперативну пам’ять - зазвичай використовуються, коли доступ до констант флеш повільний / складний (наприклад, AVR), наприклад, компілятор AVR IAR вимагає _ _flash кваліфікатора, щоб не копіювати константу в оперативну пам'ять)


Спасибі Майку. Так, я вже перевірив це - його називають опцією "Константи, що записуються" для компілятора M16C IAR C. Він копіює константи з ПЗУ в ОЗУ. Ця опція не встановлена ​​для мого проекту. Але дійсно дійсний чек! Спасибі.
IntelliChick

1

Якщо ваш процесор не має апаратної підтримки для параметра / локального стека, але компілятор намагається все-таки реалізувати стек параметрів виконання, і якщо ваш код не потребує повторного введення, ви можете зберегти код простір шляхом статичного розподілу автоматичних змінних. У деяких випадках це потрібно робити вручну; в інших випадках директиви компілятора можуть це зробити. Для ефективного ручного розподілу буде потрібно обмін змінними між процедурами. Такий обмін повинен здійснюватися обережно, щоб переконатися, що жодна програма не використовує змінну, яку інша рутина вважає "за обсягом", але в деяких випадках переваги розміру коду можуть бути значними.

Деякі процесори мають умовні виклики, які можуть зробити деякі стилі передачі параметрів більш ефективними, ніж інші. Наприклад, на контролерах PIC18, якщо підпрограма приймає один однобайтовий параметр, вона може передаватися в регістр; якщо це займає більше, всі параметри повинні бути передані в оперативній пам'яті. Якщо звичайна програма прийме два однобайтові параметри, можливо, найефективніше "передавати" один у глобальній змінній, а потім передавати інший як параметр. При широко використовуваних процедурах заощадження можуть збільшити. Вони можуть бути особливо значущими, якщо параметр, переданий через глобальний, є однобітним прапором або якщо він, як правило, має значення 0 або 255 (оскільки існують спеціальні інструкції для зберігання 0 або 255 в ОЗУ).

У ARM введення глобальних змінних, які часто використовуються разом у структурі, може значно зменшити розмір коду та підвищити продуктивність. Якщо A, B, C, D і E є окремими глобальними змінними, то код, який використовує всі, повинен завантажувати адресу кожного в реєстр; якщо регістрів недостатньо, можливо, доведеться перезавантажувати ці адреси кілька разів. На противагу цьому, якщо вони є частиною тієї самої глобальної структури MyStuff, то код, який використовує MyStuff.A, MyStuff.B, тощо, може просто завантажити адресу MyStuff один раз. Великий виграш.


1

1. Якщо ваш код покладається на безліч структур, переконайтеся, що члени структури впорядковані з тих, які займають найбільше пам'яті.

Наприклад: "uint32_t uint16_t uint8_t" замість "uint16_t uint8_t uint32_t"

Це забезпечить мінімальну обшивку структури.

2. Використовуйте const для змінних, де це можливо. Це забезпечить, що ці змінні будуть знаходитися в ПЗУ, а не з'їдати оперативну пам'ять


1

Кілька (можливо очевидних) хитрощів, які я успішно використовував для стискання коду клієнта:

  1. Компактні прапори в бітові поля або бітові маски. Це може бути вигідним, оскільки зазвичай булеві файли зберігаються як цілі числа, таким чином витрачаючи пам'ять. Це дозволить заощадити і оперативну пам'ять, і ROM, і зазвичай це не робиться компілятором.

  2. Шукайте надмірність у коді та використовуйте петлі або функції для виконання повторних операторів.

  3. Я також врятував деякий ПЗУ, замінивши багато if(x==enum_entry) <assignment>висловлювань констант індексованим масивом, подбавши про те, щоб записи перерахунків могли використовуватися як індекс масиву


0

Якщо ви можете, використовуйте вбудовані функції або макроси компілятора замість малих функцій. Є розмір і швидкість накладних витрат з передачею аргументів, і такі, які можна усунути, зробивши функцію вбудованою.


1
Будь-який гідний компілятор повинен робити це автоматично для функціональних файлів, які викликаються лише один раз.
mikeselectricstuff

5
Я виявив, що вкладка зазвичай корисніша для оптимізації швидкості, і зазвичай ціною збільшених розмірів.
Крейг МакКуїн

розміщення коду, як правило, збільшуватиме розмір коду, за винятком тривіальних функцій на кшталтint get_a(struct x) {return x.a;}
Дмитро Григор'єв,

0

Змініть локальні змінні, щоб вони були однакового розміру ваших регістрів процесора.

Якщо процесор 32-розрядний, використовуйте 32-бітні змінні, навіть якщо значення max ніколи не перевищить 255. Я використовував 8-бітну змінну, компілятор додасть код для маскування верхніх 24 біт.

Перше місце, на яке я звернувся б, - це змінні for-loop.

for( i = 0; i < 100; i++ )

Це може здатися хорошим місцем для 8-бітної змінної, але 32-бітова змінна може створювати менше коду.


Це може зберегти код, але він з'їсть оперативну пам'ять.
mikeselectricstuff

Він буде їсти оперативну пам’ять лише в тому випадку, якщо виклик цієї функції знаходиться в найдовшій гілці сліду виклику. Інакше це повторне використання простору стеку, яке вже потрібна якась інша функція.
Роберт

2
Зазвичай вірно, якщо це локальна змінна. Якщо не вистачає оперативної пам’яті, розмір глобальних варіантів, особливо масивів, є хорошим місцем для початку пошуку економії.
mikeselectricstuff

1
Інша можливість, що цікаво, полягає в заміні непідписаних змінних на підписані. Якщо компілятор оптимізує непідписаний короткий 32-розрядний регістр, він повинен додати код, щоб переконатися, що його значення переходить від 65535 до нуля. Однак, якщо компілятор оптимізує підписаний короткий список до регістра, такий код не потрібен. Оскільки немає гарантії, що станеться, якщо короткий показник буде збільшений за межею 32767, компіляторам не потрібно видавати код для вирішення цього питання. Як мінімум два компілятори ARM, на які я дивився, короткий код з підписами може бути меншим, ніж код без підпису.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.