Запросить параметры -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 архив:

  1. во-первых, gcc и g++ используются с -ffunction-sections-fdata-sections флаги
  2. тогда все .o объекты помещаются в .a архивировать с ar rcs file.a *.o
  3. наконец, линкер вызывается с -Wl,-gc-sections,-u,main опции
  4. для всех оптимизация установлена ​​на -Os,

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

Для статической библиотеки с -Wl,-gc-section удаление неиспользуемой секции, скорее всего, приведет к небольшому увеличению.

Другие вопросы по тегам