Комментарии 46
- Optimize for size -Os
- Place functions in their own sections --ffunction-sections
Так вам надо, всё таки, первое или второе?
When you specify these options, the assembler and linker will create larger object and executable files and will also be slower.
Ну, да -Wl,--gc-sections выкинет неиспользуемые секции, а с ними и неиспользуемые функции в своих секциях, но не лучше ли будет с LTO и без выделения собственных секций каждой функции? А в комбинации с LTO?
Если отключить только -Wl,--gc-sections. Размер прошивки увеличивается до 22к.
Если отключить и то и другое, то прошивка тоже 22к
Минимальный размер получается, когда включены все опции.
UPD.
Нашел опцию -flto. Добавил ее к компилятору и линкеру. Размер прошивки уменьшился до 7632 байт. Наличие или отсутствие --ffunction-sections при этом никак не влияет.
Это удивительно. Проверю, осталась ли прошивка работающей и обновлю статью.
UPD
С опцией -flto прошивка перестала работать
Если отключить только… Если отключить и то и другое, то прошивка тоже 22к
Я про это не спрашивал, ответ очевиден.
Размер прошивки уменьшился до 7632 байт.
Я вот про это.
Наличие или отсутствие --ffunction-sections при этом никак не влияет.
Ожидаемо. Это я на всякий случай, для чистоты эксперимента, спросил.
Это удивительно.
Это не удивительно, это LTO — link time optimization — код функций вытягивается из объектных модулей, а не линкуются секции, содержащие символ, целиком.
С опцией -flto прошивка перестала работать
Вот лучше найти, что не так в коде… А вы всё собирали с LTO? Эту опцию нужно и компилятору и компоновщику:
To use the link-time optimizer, -flto and optimization options should be specified at compile time and during the final link. It is recommended that you compile all the files participating in the same link with the same options and also specify those options at link time.
И линкуйте gcc, а не ld. Ну и посмотрите на прочие опции, связанные с LTO (и на -fwhole-program, в часности).
P.S. Только не удаляйте вариант без LTO из сравнения, если с LTO у вас получится запустить: LTO сравнительно новая функция и полезно видеть сравнение классической линковки с LTO.
И линкуйте gcc
А это как?
Ну и посмотрите на прочие опции, связанные с LTO (и на -fwhole-program, в часности).
Я не разбираюсь в опциях gcc. На какие опции еще посмотреть?
Это как по умолчанию GNU Make делает:
$ make -p | fgrep LINK.o
make: *** No targets specified and no makefile found. Stop.
LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH)
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
— То есть для линковки не ld вызывается напрямую, а через gcc. Но, думаю, не в этом дело.
Вам там ниже посоветовали указывать разный LTO уровень для ARMCC, то же можно и GCC: -flto=n. Ну и стоит взять GCC поновее и попробовать с ним.
Еще можно поиграть с developer.arm.com/documentation/101754/0615/armlink-Reference/armlink-Command-line-Options/--lto-level
С опцией -flto прошивка перестала работать
я правильно понимаю, что у вас gcc7? актуальная версия gcc10, я бы начал с попытки собрать с ней.
UPD
С опцией -flto прошивка перестала работать
Флаг -flto не работает вместе с HAL из-за бага который не воспринимает weak из ассемблера stm32
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83967
если убрать все ассемблерные вставки, будет работать, проверял
-fsection-anchors
-fno-move-loop-invariants
Ну так в контрольник шьётся не 'executable file', а бинарник, который собственно из 'executable file' получается мержем всего-всего в, собственно, бинарник.
Версия gcc тоже указана как-то криво (обычно версия gcc — это число вида x.y.z, например 9.3.0, можно получить запустив gcc с опцией --version).
Ещё я бы попробовал -fno-pie -no-pie, если почему-то дефолт в gcc был генерить перемещаемый код, то таким макаром ещё размер чуть сократится.
$readelf firmware.elf -a
Там вы увидите много интересного в коде, ниже выдержки:
Таблица символов «.symtab» содержит 192 элемента:
Чис: Знач Разм Тип Связ Vis Индекс имени
08000561 188 FUNC GLOBAL DEFAULT 2 __call_exitprocs
...
08000629 4 FUNC GLOBAL DEFAULT 2 __retarget_lock_[...]
...
0800049d 196 FUNC GLOBAL DEFAULT 2 __register_exitproc
...
080003d9 40 FUNC GLOBAL DEFAULT 2 exit
...
Это большая часть того, что мне не нужно, там ещё мелочи полно. Итого минимум 500 байт не нужного в микроконтроллере. От части можно избавится ключем -nostartfiles, написав замену __libc_init_array(). Можно ещё использовать -nostdlib, написав замену memcpy, memset и некоторым другим функциям, либо подтянув другую реализацию стандартной библиотеки, но это жестковато.
Результаты примерно такие:
text data bss dec hex filename
1628 224 1760 3612 e1c blink_stm32f407_disco.elf
После выкидывания ненужного
text data bss dec hex filename
716 104 1576 2396 95c blink_stm32f407_disco.elf
Размер таких маленьких проектов сильно зависит от реализации Run-Time библиотеки. Показательными будут проекты побольше, например LWIP впихнуть и графическую либину.
Также нужно понимать что уровни оптимизации являются набором отдельных флагов, совершенно разных для разных компиляторов. При правильном наборе флагов разница будет 1.5-2%, может и меньше.
#define USE_FULL_ASSERT 1
в
stm32...xx_hal_conf.h?
Помнится, у IAR был как-то отдельно включаемый агрессивный multi-file compilation режим, рассматривающий все файлы проекта как единое целое и инлайнящий всё и вся.
А вот выводы… Я бы не стал вот так брать и ставить gcc на последнее место в компиляторостроении.
По факту USB Device + Masstorage Class, которые тянут аж на 14K это крутой результат. Единственное чего не хватает для полного ужаса — это требований по RAM. Но это уводит нас в другую область. А уходить туда совсем не хочется.
По вопросу статьи скажу так — результат вышел очень приближенным. По сути сравнивать работу именно кодогенератора компилятора надо вычислив размер скомпилорованного кода скажем по MAP файлу. Тут на результат сильно влияет runtime библиотека.
Я не скажу ничего про armcc (слишко мало с ним работал). Но между жестко посаженной на диету и сильно оптимизированной библиотекой IAR и нацеленной на переносимость (включая кроссплатформенную) библиотекой GCC есть огромная разница.
Что использовать решает программист (если ему дадут — тут бюджет разработки подвязан в виде цены компилятора) и руководители. У меня последнее время золотое правило — IAR и GCC проект должны собирать. Работающим. Потому как IAR штука хорошая, но… Думаю это «но» достаточно хорошо известно Российским разработчикам. А любая разработка рано или поздно должна вырасти до того момента, когда это «но» оказывается существенным. А опыт учит, что случается это сильно раньше чем планируется.
Вот кусок map файла из IAR. Тут из стандартной библиотеки немного осталось.
dl7M_tln.a: [2]
exit.o 4
------------------------------------------------
Total: 4
rt7M_tl.a: [3]
ABImemclr4.o 6
ABImemcpy_small.o 24
ABImemset48.o 50
XXexit.o 12
cexit.o 10
cmain.o 30
cstartup_M.o 12
data_init.o 40
lz77_init_single.o 122
zero_init3.o 58
------------------------------------------------
Total: 364
Да, у IAR есть классная штука. Он пакует данные для инициализации. lz77_init_single.o — это распаковщик. Не знаю, умеют ли так делать другие компиляторы
Specifies how to handle the initializers. Choose between:
none — Disables compression of the selected section contents. This is the default method for initialize manually.
zeros — Compresses consecutive bytes with the value zero.
packbits — Compresses with the PackBits algorithm. This method generates good results for data with many identical consecutive bytes.
lz77 — Compresses with the Lempel-Ziv-77 algorithm. This method handles a larger variety of inputs well, but has a slightly larger decompressor.
auto — ILINK estimates the resulting size using each packing method (except for auto), and then chooses the packing method that produces the smallest estimated size. Note that the size of the decompressor is also included. This is the default method for initialize by copy.
smallest — This is a synonym for auto.
Да, у IAR есть классная штука. Он пакует данные для инициализации. lz77_init_single.o — это распаковщик. Не знаю, умеют ли так делать другие компиляторы
Кейл вроде умеет — https://www.keil.com/support/man/docs/armlink/armlink_pge1362065930871.htm
В рамках одного большого проекта по пересборке дизассемблированного кода получился вот такой эксперимент:
- прошивку размером порядка 28КБ, изначально собранную неизвестной версией IAR, отдизассемблировали в большой монолитный asm и «доработали напильником» до сборки средствами gcc/ac5/ac6/свежего IAR байт в байт (само собой, под каждый компилятор выходной файл генерировался специфическим для него способом, синтаксис разный, особенно у IAR)
- затем из получившегося asm полностью выкорчевали STM32 SPL, объявили всё пропавшее как extern и включили в сборку исходники SPL.
- собрали каждым компилятором (уже как asm + C, SPL компилировалась из С, а остальное было неизменным из asm, включая стандартную библиотеку, стартовый код и упакованную секцию данных)
Результаты были такие: свежий IAR выдал ещё немного меньший код, чем оригинал, а остальные в старые размеры не поместились несмотря ни на какие игры с настройками.
ARMCC v6.14.1 (среда Keil uVision 5.32)
Вроде бы формально он называется не armcc, а armclang.
Надо отметить, что опция KEIL «Use cross module optimization» Значительно увеличила время компиляции, но ни чуть не уменьшила размер кода.
Попробуйте (с armcc5) компилятору и линкеру вручную указать опцию --feedback=unused
и дважды перекомпилировать все.
-flto мощный инструмент, дающий выигрыш по размеру и скорости выполнения. Выводы Вашей статьи могут кардинально измениться :).
GCC в связке с CubeMX требует немножко танцев с бубном.
Основные возможные проблемы связаны с ".weak" и возможной "переоптимизацией" глобальных переменных.
В файле (например) startup_stm32g431xx.s закомментите все(!) ".thumb_set" типа:
.weak NMI_Handler
//.thumb_set NMI_Handler,Default_Handler
Чаще всего этого будет достаточно.
Подозреваю, что есть более элегантное решение, пока поборол только так.
Удачи.
Итак, в startup_xx файле надо убрать weak объявления используемых векторов. В данном случае используются только два вектора:
// .weak USB_LP_CAN1_RX0_IRQHandler
// .thumb_set USB_LP_CAN1_RX0_IRQHandler,Default_Handler
// .weak SysTick_Handler
// .thumb_set SysTick_Handler,Default_Handler
В результате размер прошивки стал 11722.
Сравнение компиляторов ARMCC, IAR и GCC