Запросить параметры -ffunction-section & -fdata-секции в gcc
Ниже указано на странице GCC для разделов функций и параметров разделов данных:
-ffunction-sections -fdata-sections
Поместите каждую функцию или элемент данных в отдельный раздел выходного файла, если цель поддерживает произвольные разделы. Имя функции или имя элемента данных определяет имя раздела в выходном файле. Используйте эти параметры в системах, где компоновщик может выполнять оптимизацию, чтобы улучшить местность ссылок в пространстве команд. Большинство систем, использующих объектный формат ELF и процессоры SPARC, работающие под управлением Solaris 2, имеют компоновщики с такой оптимизацией. AIX может иметь эти оптимизации в будущем.
Используйте эти параметры только в том случае, если это дает значительные преимущества. Когда вы укажете эти параметры, ассемблер и компоновщик создадут большие объектные и исполняемые файлы, а также будут работать медленнее. Вы не сможете использовать gprof на всех системах, если вы укажете эту опцию, и у вас могут возникнуть проблемы с отладкой, если вы укажете и эту опцию, и -g.
У меня сложилось впечатление, что эти параметры помогут уменьшить размер исполняемого файла. Почему на этой странице написано, что она будет создавать исполняемые файлы большего размера? Я что-то пропустил?
5 ответов
При использовании этих опций компилятора вы можете добавить опцию компоновщика -Wl,--gc-sections
это удалит весь неиспользуемый код.
Интересно, используя -fdata-sections
может сделать буквальные пулы ваших функций, и, следовательно, сами ваши функции больше. Я заметил это, в частности, на ARM, но, вероятно, это будет справедливо в других местах. Бинарный файл, который я тестировал, вырос только на четверть процента, но вырос. Глядя на разборку измененных функций было понятно почему.
Если все записи BSS (или DATA) в вашем объектном файле выделены одному разделу, компилятор может сохранить адрес этого раздела в пуле литералов функций и сгенерировать нагрузки с известными смещениями с этого адреса в функции для доступа к вашему разделу. данные. Но если вы включите -fdata-sections
он помещает каждый фрагмент данных BSS (или DATA) в свой собственный раздел, и, поскольку он не знает, какой из этих разделов может быть впоследствии собран сборщиком мусора, или в каком порядке компоновщик разместит все эти разделы в конечном исполняемом образе, он больше не может загружать данные, используя смещения с одного адреса. Поэтому вместо этого он должен выделить запись в буквальном пуле для используемых данных, и как только компоновщик выяснил, что входит в конечное изображение и куда, тогда он может пойти и исправить эти записи буквального пула с фактическим адресом данные.
Так что да, даже с -Wl,--gc-sections
результирующее изображение может быть больше, потому что фактический текст функции больше.
Ниже я добавил минимальный пример
Приведенного ниже кода достаточно, чтобы увидеть поведение, о котором я говорю. Пожалуйста, не поддавайтесь изменчивому объявлению и использованию глобальных переменных, которые в реальном коде сомнительны. Здесь они обеспечивают создание двух разделов данных при использовании -fdata-section.
static volatile int head;
static volatile int tail;
int queue_empty(void)
{
return head == tail;
}
Версия GCC, используемая для этого теста:
gcc version 6.1.1 20160526 (Arch Repository)
Во-первых, без -fdata-section мы получаем следующее.
> arm-none-eabi-gcc -march=armv6-m \
-mcpu=cortex-m0 \
-mthumb \
-Os \
-c \
-o test.o \
test.c
> arm-none-eabi-objdump -dr test.o
00000000 <queue_empty>:
0: 4b03 ldr r3, [pc, #12] ; (10 <queue_empty+0x10>)
2: 6818 ldr r0, [r3, #0]
4: 685b ldr r3, [r3, #4]
6: 1ac0 subs r0, r0, r3
8: 4243 negs r3, r0
a: 4158 adcs r0, r3
c: 4770 bx lr
e: 46c0 nop ; (mov r8, r8)
10: 00000000 .word 0x00000000
10: R_ARM_ABS32 .bss
> arm-none-eabi-nm -S test.o
00000000 00000004 b head
00000000 00000014 T queue_empty
00000004 00000004 b tail
От arm-none-eabi-nm
мы видим, что queue_empty имеет длину 20 байтов (14 шестнадцатеричных), и arm-none-eabi-objdump
вывод показывает, что в конце функции есть одно слово перемещения, это адрес раздела BSS (раздел для неинициализированных данных). Первая инструкция в функции загружает это значение (адрес BSS) в r3. Следующие две инструкции загружаются относительно r3, смещаясь на 0 и 4 байта соответственно. Эти две нагрузки являются нагрузками значений головы и хвоста. Мы можем увидеть эти смещения в первом столбце вывода из arm-none-eabi-nm
, nop
в конце функции заключается в выравнивании слов адреса буквального пула.
Далее мы увидим, что происходит, когда добавляется -fdata-section.
arm-none-eabi-gcc -march=armv6-m \
-mcpu=cortex-m0 \
-mthumb \
-Os \
-fdata-sections \
-c \
-o test.o \
test.c
arm-none-eabi-objdump -dr test.o
00000000 <queue_empty>:
0: 4b03 ldr r3, [pc, #12] ; (10 <queue_empty+0x10>)
2: 6818 ldr r0, [r3, #0]
4: 4b03 ldr r3, [pc, #12] ; (14 <queue_empty+0x14>)
6: 681b ldr r3, [r3, #0]
8: 1ac0 subs r0, r0, r3
a: 4243 negs r3, r0
c: 4158 adcs r0, r3
e: 4770 bx lr
...
10: R_ARM_ABS32 .bss.head
14: R_ARM_ABS32 .bss.tail
arm-none-eabi-nm -S test.o
00000000 00000004 b head
00000000 00000018 T queue_empty
00000000 00000004 b tail
Сразу же мы видим, что длина queue_empty увеличилась на четыре байта до 24 байтов (18 шестнадцатеричных) и что теперь необходимо выполнить два перемещения в буквальном пуле queue_empty. Эти перемещения соответствуют адресам двух созданных разделов BSS, по одному для каждой глобальной переменной. Здесь должно быть два адреса, потому что компилятор не может знать относительную позицию, в которой компоновщик в конечном итоге поместит эти два раздела. Глядя на инструкции в начале queue_empty, мы видим, что есть дополнительная загрузка, компилятор должен генерировать отдельные пары нагрузки, чтобы получить адрес раздела и затем значение переменной в этом разделе. Дополнительная инструкция в этой версии queue_empty не удлиняет тело функции, она просто занимает место, которое раньше было nop, но в общем случае это не так.
Ты можешь использовать -ffunction-sections
а также -fdata-sections
в статических библиотеках, что увеличит размер статической библиотеки, поскольку каждая функция и глобальная переменная данных будут помещены в отдельный раздел.
А потом использовать -Wl,--gc-sections
на программу, связывающую эту статическую библиотеку, которая удалит неиспользуемые разделы.
Таким образом, конечный двоичный файл будет меньше, чем без этих флагов.
Будьте осторожны, так как -Wl,--gc-sections
может сломать вещи.
Я получаю лучшие результаты, добавив дополнительный шаг и построив .a
архив:
- во-первых, gcc и g++ используются с
-ffunction-sections
-fdata-sections
флаги - тогда все
.o
объекты помещаются в.a
архивировать сar rcs file.a *.o
- наконец, линкер вызывается с
-Wl,-gc-sections,-u,main
опции - для всех оптимизация установлена на
-Os
,
Я попробовал это некоторое время назад и, глядя на результаты, кажется, что увеличение размера происходит от порядка объектов с различным выравниванием. Обычно компоновщик сортирует объекты так, чтобы отступы между ними были небольшими, но похоже, что он работает только внутри раздела, а не между отдельными разделами. Таким образом, вы часто получаете дополнительные отступы между разделами данных для каждой функции, увеличивая общее пространство.
Для статической библиотеки с -Wl,-gc-section удаление неиспользуемой секции, скорее всего, приведет к небольшому увеличению.