Ответ 1
Пример минимального запуска
Здесь я приведу минимальный пример и скомпилирую его двумя способами для получения функционально идентичных исполняемых файлов:
- один объединенный файл
f12.c
без частичной ссылки наf12.o
- два отдельных
f1.c
иf2.c
, которые сначала частично связаны вf12_r.o
main.c
#include <assert.h>
#include <stdlib.h>
int f_1_2(void);
int f_2_1(void);
int main(void) {
assert(f_1_2() + f_2_1() == 5);
return EXIT_SUCCESS;
}
f1.c
#include "f1.h"
f2.c
#include "f2.h"
f12.c
#include "f1.h"
#include "f2.h"
f1.h
int f_2(void);
int f_1_2(void) {
return f_2() + 1;
}
int f_1(void) {
return 1;
}
f2.h
int f_1(void);
int f_2_1(void) {
return f_1() + 1;
}
int f_2(void) {
return 2;
}
run.sh
#!/usr/bin/env bash
set -eux
cflags='-ggdb3 -std=c99 -O0 -fPIE -pie'
gcc $cflags -c -o f1.o f1.c
gcc $cflags -c -o f2.o f2.c
gcc $cflags -c -o f12.o f12.c
ld -o f12_r.o -r f1.o f2.o
gcc $cflags -c -o main.o main.c
gcc $cflags -o main.out f12.o main.o
gcc $cflags -o main_r.out f12_r.o main.o
./main.out
./main_r.out
Если мы попробуем то же самое, но без ld -r
, мы получим последние предупреждения:
+ ld -o f12_r.o f1.o f2.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
+ gcc -ggdb3 -std=c99 -O0 -fPIE -pie -o main_r.out f12_r.o main.o
/usr/bin/ld: error in f12_r.o(.eh_frame); no .eh_frame_hdr table will be created
ни один из них не заставляет инструмент выходить не равным 0, а финальный исполняемый файл все еще выполняется, поэтому я не уверен, насколько он плох. ТОДО понимаю.
Двоичный анализ
Если вы не знакомы с перемещением, сначала прочтите это: Что делают компоновщики?
Ключевой вопрос заключается в том, как частичная ссылка может ускорить ссылку. Единственное, о чем я мог думать, - это разрешать ссылки в предварительно связанных файлах. Я сосредоточился на этом сейчас.
Однако это не выполняется в соответствии с указаниями на: Разрешить относительные перемещения в частичной ссылке, поэтому я ожидаю, что это не значительно ускорит ссылку.
Я подтвердил это с помощью:
objdump -S f12.o
objdump -S f12_r.o
оба из которых производят идентичные выходные данные, которые содержат:
int f_1_2(void) {
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
return f_2() + 1;
4: e8 00 00 00 00 callq 9 <f_1_2+0x9>
9: 83 c0 01 add $0x1,%eax
}
c: 5d pop %rbp
d: c3 retq
поэтому мы видим, что вызов f_1_2
еще не был разрешен ни в одном из случаев, поскольку адрес относительного смещения по-прежнему равен 0: e8 00 00 00 00
(e8
- это код операции).
Это также научило меня, что GCC не разрешает вызовы функций до окончательной ссылки, либо обоснование TODO, возможно ли принудительно разрешить его?
Benchmark
Я сравнил LD и GOLD по адресу: Замена ld золотом - есть опыт?, поэтому я решил использовать его повторно, чтобы посмотреть, не приведет ли частичная ссылка к какому-либо ускорению ссылки.
Я сгенерировал тестовые объекты с помощью этого сценария:
./generate-objects 100 1000 100
а затем я начал с самого экстремального варианта из возможных ссылок: предварительно связать все, кроме основного файла, а затем сравнить конечную ссылку:
mv main.o ..
ld -o partial.o -r *.o
time gcc partial.o ../main.o
time gcc -fuse-ld=gold partial.o ../main.o
Результаты в виде настенных часов в секундах были следующими:
No partial link Partial link
No Gold 6.15 5.756
Gold 4.06 4.457
Поэтому:
- разница во времени существует, но она не очень значительна
- без золота он шел быстрее, а с золотом - медленнее!
Поэтому, исходя из этого эксперимента, кажется, что частичное связывание может вообще не ускорить ваше соединение, и я бы просто рекомендовал вам вместо этого попробовать GOLD.
Дайте мне знать, если вы можете привести конкретный пример, где добавочное связывание приводит к значительному ускорению.
Пример из практики: ядро Linux
Ядро Linux является одним из примеров большого проекта, в котором раньше использовались инкрементные ссылки, поэтому, возможно, мы сможем что-то из этого извлечь.
С тех пор он переместился в ar T
тонкие архивы, как показано на: https://unix.stackexchange.com/questions/5518/what-is-the-difference-between-the-following-kernel-makefile-terms-vmlinux-vml/482978#482978
Первоначальная фиксация и обоснование находятся по адресу: a5967db9af51a84f5e181600954714a9e4c69f1f (включена в v4.9
), сообщение о фиксации которого гласит:
ld -r is an incremental link used to create built-in.o files in build
subdirectories. It produces relocatable object files containing all
its input files, and these are are then pulled together and relocated
in the final link. Aside from the bloat, this constrains the final
link relocations, which has bitten large powerpc builds with
unresolvable relocations in the final link.
это также упоминается в Documentation/process/changes.rst:
Binutils
--------
The build system has, as of 4.13, switched to using thin archives ('ar T')
rather than incremental linking ('ld -r') for built-in.a intermediate steps.
This requires binutils 2.20 or newer.
TODO: узнайте, когда было введено инкрементное связывание, и посмотрите, есть ли минимальный тестовый пример, который мы можем использовать, чтобы увидеть его ускорение: https://unix.stackexchange.com/questions/491312/why-does-the-linux-kernel-build-system-use-incremental-linking-or-ar-t-thin-arch
Протестировано на Ubuntu 18.10, GCC 8.2.0, ноутбуке Lenovo ThinkPad P51, процессоре Intel Core i7-7820HQ (4 ядра /8 потоков), 2x оперативной памяти Samsung M471A2K43BB1-CRC (2x 16 ГБ), Samsung MZVLB512HAJQ-000L7 SSD (3000 МБ/с)).