Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Программирование и алгоритмические языки


Ulrich Drepper "Как писать разделяемые библиотеки"
Назад Оглавление Вперед

2. Оптимизация объектов DSO

В этом разделе мы опишем различные варианты оптимизации, основанные на использовании переменных или функций языков C или C++. Везде, где явно не указывается, выбирается ли переменная или функция, это делается сознательно, поскольку многие реализации пригодны как для первого, так и для второго случая. Но есть некоторые варианты архитектуры, где функции обрабатываются как переменные. В основном это относится к встроенным вариантам RISC-архитектуры, таким как SH-3 и SH-4, в которых есть ограничения в режимах адресации, что не позволяет реализовать обработку функций также, как для других архитектур. В большинстве случаев можно без проблем одновременно использовать оптимизацию переменных и функций. На самом деле это то, что надо делать всегда с тем, чтобы добиться лучшей производительности для всех вариантов архитектур.

Наиболее важная рекомендация состоит в том, чтобы при генерации кода, которая завершается созданием объектов DSO, всегда использовать параметр -fpic или -fPIC. Это относится как к данным, так и к коду. В коде, который компилируется без этих параметров, почти наверняка будут перемещения текстовых секций. Это ничем не оправданные затраты. Перемещение текстовых секций требуют от динамического компоновщика дополнительной работы. И утверждение о том, что код не является разделяемым, поскольку объект DSO не используется ни в каком другом процессе, неверно. В подобном случае само использование объекта DSO не дает, прежде всего, никаких преимуществ; этот код должен быть просто добавлен в код приложения.

Некоторые пытаются утверждать, что применение параметров -fpic/-fPIC для определенных вариантов архитектуры имеет слишком много недостатков. Эта аргументация касается, в основном, архитектуре IA-32. Здесь использование %ebx в качестве регистра PIC лишает компилятор одного из драгоценных регистров, который можно использовать для оптимизации. Но это действительно не такая уж большая проблема. Во-первых, отсутствие регистра %ebx никогда не было большой потерей. Во-вторых, в современных компиляторах (например, gcc версии выше 3.1) обработка регистра PIC происходит более гибко. Использование регистра %ebx требуется не всегда, что может помочь избежать ненужных операции копирования. И, в-третьих, если компилятору предоставить гораздо больше информации так, как это будет показано дальше в настоящем разделе, то можно освободить регистр PIC от лишней нагрузки. Все это в совокупности потребует накладных расходов, которые в большинстве ситуаций несущественны.

Когда используется компилятор gcc, то с помощью параметров -fpic/-fPIC компилятору можно также сообщить, что не нужно выполнять ряд оптимизаций, которые допустимы для исполняемого модуля. Здесь речь идет о сокращении процесса поиска символа. Поскольку компилятор может сделать предположение, что исполняемый модуль будет первым объектом в области поиска, то он может решить, что все ссылки глобальных символов могут быть разрешены локально. Доступ к локально определенной переменной, можно выполнить напрямую без использования косвенного доступа через таблицу GOT. Это неверно для объектов DSO: объекты DSO в области поиска могут находиться дальше и, из-за этого, разрешение может быть выполнено с объектами, которые будут обнаружены раньше. Поэтому добавление параметров -fpic/-fPIC является обязательным при компиляции любого кода, который потенциально может оказаться в объекте DSO, т. к. в противном случае объект DSO не сможет работать правильно. В компиляторе нет параметра, с помощью которого можно было отделить этот вариант оптимизации от генерации кода, не зависящего от своего местоположения.

Какой из двух параметров -fpic или -fPIC должен использоваться, нужно решать индивидуально в каждом конкретном случае. Для некоторых варианта архитектуры разницы вообще нет, и разработчики, как правило, не задумываются об использовании этих параметров. В большинстве вариантов RISC-архитектуры различие большое различие. В качестве примера приведем код чтения глобальной переменной global, который сгенерирован компилятором gcc с использованием параметра -fpic:

sethi %hi(_GLOBAL_OFFSET_TABLE_-4),%l7
call   .LLGETPC0
add    %l7,%lo(_GLOBAL_OFFSET_TABLE_+4),%l7
ld      [%l7+global],%g1
ld      [%g1],%g1

И та же самая последовательность кода при использовании параметра -fPIC:

sethi %hi(_GLOBAL_OFFSET_TABLE_-4),%l7
call   .LLGETPC0
add    %l7,%lo(_GLOBAL_OFFSET_TABLE_+4),%l7
sethi %hi(global),%g1
or      %g1,%lo(global),%g1
ld      [%l7+%g1],%g1
ld      [%g1],%g1

В обоих случаях инструкция %l7 сначала загружает адрес таблицы GOT. Затем происходит доступ к таблице GOT с тем, чтобы получить адрес переменной global. Если в случае использования параметра -fpic достаточно одной инструкции, то в случае использования параметра -fPIC необходимы три инструкции. Параметр -fpic указывает компилятору, что размер таблицы GOT не превышает определенного значения, конкретной для каждого варианта архитектуры (8 кбайт в случае архитектуры SPARC). Только в самой инструкции может быть закодировано, что смещение от начала таблицы GOT будет присутсвовать во многих записях GOT, то есть в инструкции ld первой последовательности кода, приведенной выше. Если используется параметр -fPIC, то такого ограничения нет, и поэтому компилятор должен исходить из пессимистических предположений и генерировать код, в котором можно использовать смещения любого размера. Исходя из разницы в количестве инструкций, в этом примере следует правильный вывод, что во всех случаях, когда нет абсолютной необходимости использовать параметр -fPIC, должен использоваться параметр -fpic. Когда ограничения будут превышены, компоновщик прекратит работу и выдаст сообщение, и нужно будет лишь перекомпилировать код.

Когда ассемблерный код пишется вручную, то легче пропустить те ситуации, где нужно использовать последовательность кода, не зависящего от местоположения. Последовательности, в которых не используется регистр PIC, выглядят и на самом деле являются более простыми и естественными. Поэтому в таких случаях крайне важно проверять, помечены ли объекты DSO как содержащие перемещения текстовых секций. Это сделать достаточно легко:

readelf -d binary | grep TEXTREL

Если в результате будут создаваться какие-нибудь текстовые перемещение, то лучше всего начать искать, из-за чего это происходит.


Предыдущий раздел:   Следующий раздел:
Назад Оглавление Вперед