Как мы выяснили в прошлой публикации, ленивое связывание (Lazy Binding) - это процесс определения адресов символов на этапе выполнения. Данный механизм ускоряет загрузку приложения ценой накладных расходов в процессе работы: вместо прямого вызова функции вызывается PLT заглушка, которая задействует набор дополнительных машинных инструкций и дергает динамический компоновщик для определения адресов.
Система ленивого связывания не везде используется по дефолту: в Windows таблица адресов (Import Address Table - IAT) полностью заполняется на старте программы, в Linux ситуация обратная, однако никто не говорить, что скорректировать такое поведение невозможно - все возможно (особенно в Linux)!
Современные компиляторы поддерживают флаг "-fno-plt", который, как видно из названия, исключает из кода plt заглушки и необходим для связывания символов на старте программы:
$ gcc -fno-plt -o prog main.c
Использование данного флага:
1) уменьшает размер исполняемого файла: каждая заглушка добавляет, как минимум, 32 байта к объему (для x86/x86_64 архитектур)
2) уменьшает загруженность регистров и "instruction cache" для CPU
3) ускоряет вызовы библиотечных функций и, как следствие, процесс выполнения кода
По данным разработчика yugr, использование флага в clang дало +10% к приросту производительности для большого проекта (автор деталей не уточнял). Это действительно хороший и значимый результат, особенно, для высоконагруженных сервисов, где каждая микросекунда на счету.
Интересный факт: во многих дистрибутивах сборка пакетов выполнена с внедрением флага "-fno-plt". Некоторые утилиты, в качестве исключений, могут обходить стороной этот флаг при компиляции. Так, например, для xorg, glibc, valgrind, openjdk (внутри Arch Linux) он не используется.
Cуществует еще 2 способа отключить ленивое связывание: переменная окружения "LD_BIND_NOW" и флаг компоновщика "-z now". При таком подходе влияние оказывается на загрузчик, из-за чего может не получится выжать максимум из производительности, т.к. вызов функций все также будут происходить через plt заглушки:
$ LD_BIND_NOW=1 ./prog
$ gcc -o prog main.c -Wl,-z,now
Результат использования флага и переменной окружения аналогичен:
1) plt заглушки все еще являются частью кода
2) все адреса символов определяются на этапе запуска программы
3) не вызывается динамический компоновщик для связывания функций при первом использовании
4) при вызове функции, переход происходит сразу на конкретный адрес в таблице GOT
Таким образом, однозначно добиться дополнительной производительности от приложения можно через использование флага компилятора "-fno-plt", который исключит из бинарного файла все plt заглушки и приведет к вызову функций через прямое обращение к GOT.
#linux