Статическое связывание против динамического связывания

StackOverflow https://stackoverflow.com/questions/1993390

Вопрос

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

1) Разница в производительности во время выполнения между статическим и динамическим связыванием обычно незначительна.

2) (1) неверно при использовании компилятора профилирования, который использует данные профиля для оптимизации горячих путей программы, поскольку при статическом связывании компилятор может оптимизировать как ваш код, так и код библиотеки.При динамическом связывании можно оптимизировать только ваш код.Если большая часть времени тратится на выполнение библиотечного кода, это может иметь большое значение.В противном случае (1) все еще применяется.

Это было полезно?

Решение

  • Динамический связывание может снизить общее потребление ресурсов (если более одного процесса используют одну и ту же библиотеку (включая, конечно, версию в «одной и той же»)).Я считаю, что именно этот аргумент определяет его присутствие в большинстве сред.Здесь «ресурсы» включают дисковое пространство, оперативную память и кэш-память.Конечно, если ваш динамический компоновщик недостаточно гибок, существует риск DLL ад.
  • Динамический связывание означает, что исправления ошибок и обновления библиотек распространять улучшить твой продукт, не требуя от вас ничего доставлять.
  • Плагины всегда звоните динамичный связывание.
  • Статический связывание означает, что вы можете быть уверены, что код будет работать очень быстро. ограниченная среда (в начале процесса загрузки или в режиме восстановления).
  • Статический связывание может создавать двоичные файлы легче распространять в различные пользовательские среды (за счет отправки более крупной и ресурсоемкой программы).
  • Статический связывание может немного позволить более быстрый запуск раз, но это в некоторой степени зависит как от размера, так и от сложности вашей программы. и о деталях стратегии загрузки ОС.

Некоторые изменения для включения очень важных предложений в комментарии и другие ответы.Хочу отметить, что то, как вы это сделаете, во многом зависит от того, в какой среде вы планируете работать.Минимальным встроенным системам может не хватать ресурсов для поддержки динамического связывания.Небольшие системы немного большего размера вполне могут поддерживать динамическое связывание, поскольку их память достаточно мала, чтобы сделать экономию оперативной памяти за счет динамического связывания очень привлекательной.Полноценные потребительские ПК обладают, как отмечает Марк, огромными ресурсами, и вы, вероятно, можете позволить проблемам удобства управлять своими мыслями по этому вопросу.


Для решения проблем производительности и эффективности: это зависит.

Классически динамические библиотеки требуют своего рода связующего слоя, который часто означает двойную отправку или дополнительный уровень косвенности при адресации функций и может стоить немного скорости (но действительно ли время вызова функции составляет большую часть вашего времени работы???).

Однако если вы запускаете несколько процессов, которые часто обращаются к одной и той же библиотеке, вы можете в конечном итоге сэкономить строки кэша (и, таким образом, выиграть в производительности) при использовании динамического связывания по сравнению с использованием статического связывания.(Если только современные ОС не достаточно умны, чтобы замечать идентичные сегменты в статически связанных двоичных файлах.Кажется сложно, кто-нибудь знает?)

Еще одна проблема:время загрузки.В какой-то момент вы оплачиваете стоимость погрузки.Когда вы платите эту стоимость, зависит от того, как работает ОС, а также от того, какую ссылку вы используете.Возможно, вы предпочтете отложить оплату до тех пор, пока не поймете, что она вам нужна.

Обратите внимание, что статическое и динамическое связывание традиционно нет проблема оптимизации, поскольку оба они требуют отдельной компиляции в объектные файлы.Однако это не обязательно:компилятор в принципе может изначально «скомпилировать» «статические библиотеки» в переваренную форму AST и «связать» их, добавив эти AST к библиотекам, сгенерированным для основного кода, тем самым обеспечивая глобальную оптимизацию.Ни одна из систем, которые я использую, не делает этого, поэтому я не могу комментировать, насколько хорошо она работает.

Способ ответа на вопросы о производительности: всегда путем тестирования (и использовать тестовую среду, максимально похожую на среду развертывания).

Другие советы

1) основан на том факте, что вызов функции DLL всегда использует дополнительный косвенный переход.Сегодня это обычно незначительно.Внутри DLL на процессоры i386 приходится больше накладных расходов, поскольку они не могут генерировать позиционно-независимый код.На amd64 переходы могут осуществляться относительно счетчика программ, так что это огромное улучшение.

2) Это правильно.С помощью оптимизации, основанной на профилировании, вы обычно можете повысить производительность примерно на 10–15 процентов.Теперь, когда скорость процессора достигла предела, возможно, стоит это сделать.

Я бы добавил:(3) компоновщик может организовать функции в более эффективную группировку кэша, чтобы свести к минимуму дорогостоящие промахи на уровне кэша.Это также может особенно повлиять на время запуска приложений (на основе результатов, которые я видел с компилятором Sun C++)

И не забывайте, что с помощью DLL невозможно устранить мертвый код.В зависимости от языка код DLL также может быть неоптимальным.Виртуальные функции всегда виртуальны, поскольку компилятор не знает, перезаписывает ли их клиент.

По этим причинам, если в DLL нет реальной необходимости, просто используйте статическую компиляцию.

РЕДАКТИРОВАТЬ (чтобы ответить на комментарий, подчеркивание пользователя)

Вот хороший ресурс о проблеме позиционно-независимого кода. http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/

Как объяснялось, в x86 их AFAIK нет ни для чего другого, кроме 15-битных диапазонов перехода, а не для безусловных переходов и вызовов.Вот почему функции (из генераторов), имеющие размер более 32 КБ, всегда были проблемой и требовали встроенных батутов.

Но в популярных ОС x86, таких как Linux, вам просто не нужно беспокоиться о том, что файл SO/DLL не создается с помощью gcc выключатель -fpic (что требует использования таблиц косвенных переходов).Потому что если вы этого не сделаете, код просто исправится, как если бы его переместил обычный компоновщик.Но при этом сегмент кода становится недоступным для совместного использования, и потребуется полное отображение кода с диска в память и обработка всего этого, прежде чем его можно будет использовать (очистка большей части кешей, попадание в TLB) и т. д.Было время, когда это считалось медленным...слишком медленно.

Так что никакой выгоды у вас больше не будет.

Я не помню, какая ОС (Solaris или FreeBSD) вызывала у меня проблемы с моей системой сборки Unix, потому что я просто этого не делал и задавался вопросом, почему она вышла из строя, пока я не применил -fPIC к gcc.

Динамическое связывание — единственный практический способ удовлетворить некоторые лицензионные требования, например LGPL.

Я согласен с пунктами, упомянутыми dnmckee, плюс:

  • Статически связанные приложения может быть проще развертывать, поскольку в них меньше или вообще отсутствуют дополнительные зависимости файлов (.dll/.so), которые могут вызвать проблемы, если они отсутствуют или установлены в неправильном месте.

Одной из причин создания статически связанной сборки является проверка того, что у вас есть полное закрытие исполняемого файла, т.е.что все ссылки на символы разрешены правильно.

В рамках большой системы, которая создавалась и тестировалась с использованием непрерывной интеграции, ночные регрессионные тесты проводились с использованием статически связанной версии исполняемых файлов.Иногда мы видели, что символ не разрешался и статическая ссылка терпела неудачу, хотя динамически связанный исполняемый файл компоновался успешно.

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

1/Я участвовал в проектах, где сравнивались динамическое и статическое связывание, и разница не была определена достаточно маленькой, чтобы переключиться на динамическое связывание (я не участвовал в тестировании, я просто знаю вывод)

2/ Динамическое связывание часто связано с PIC (код, независимый от позиции, код, который не нужно изменять в зависимости от адреса, по которому он загружен).В зависимости от архитектуры PIC может вызвать еще одно замедление, но он необходим для того, чтобы получить выгоду от совместного использования динамически связанной библиотеки между двумя исполняемыми файлами (и даже двумя процессами одного и того же исполняемого файла, если ОС использует рандомизацию адреса загрузки в качестве меры безопасности).Я не уверен, что все ОС позволяют разделить эти два понятия, но Solaris и Linux позволяют это сделать, а ISTR - это HP-UX.

3/ Я участвовал в других проектах, в которых использовалось динамическое связывание для функции «легкого исправления».Но этот «легкий патч» делает распространение небольших исправлений немного проще, а сложных — кошмаром управления версиями.Часто нам приходилось продвигать все подряд, а также отслеживать проблемы на сайте клиента, потому что неправильная версия была токеном.

Мой вывод заключается в том, что я использовал статическую ссылку, за исключением:

  • для таких вещей, как плагины, которые зависят от динамического связывания

  • когда важно совместное использование (большие библиотеки, используемые несколькими процессами одновременно, например среда выполнения C/C++, библиотеки графического интерфейса и т. д.)которые часто управляются независимо и для которых строго определен ABI)

Если кто-то хочет использовать «легкий патч», я бы сказал, что библиотеками нужно управлять так же, как большими библиотеками выше:они должны быть почти независимыми с определенным ABI, который не должен изменяться исправлениями.

Этот подробно обсудим общие библиотеки в Linux и влияние на производительность.

Это довольно просто, правда.Когда вы вносите изменения в исходный код, вы хотите подождать 10 минут, пока он будет собран, или 20 секунд?Двадцать секунд — это все, что я могу выдержать.Помимо этого, я либо достаю меч, либо начинаю думать о том, как я могу использовать отдельную компиляцию и компоновку, чтобы вернуть его в зону комфорта.

В Unix-подобных системах динамическое связывание может затруднить использование пользователем приложения с общими библиотеками, установленными в отдаленных местах.Это связано с тем, что динамический компоновщик обычно не обращает внимания на LD_LIBRARY_PATH или его эквивалент для процессов с привилегиями root.Иногда статическое связывание спасает положение.

Альтернативно, процесс установки должен найти библиотеки, но это может затруднить сосуществование нескольких версий программного обеспечения на компьютере.

Динамическое связывание требует дополнительного времени для ОС, чтобы найти динамическую библиотеку и загрузить ее.При статической компоновке все собрано вместе и выполняется однократная загрузка в память.

Также см DLL Ад.Это сценарий, в котором DLL, загружаемая ОС, не является той, которая поставляется с вашим приложением, или той версией, которую ожидает ваше приложение.

Лучшим примером динамического связывания является ситуация, когда библиотека зависит от используемого оборудования.В древние времена математическую библиотеку C считали динамической, чтобы каждая платформа могла использовать все возможности процессора для ее оптимизации.

Еще лучшим примером может быть OpenGL.OpenGl — это API, который AMD и NVidia реализуют по-разному.И вы не сможете использовать реализацию NVidia на карте AMD, потому что оборудование другое.Из-за этого вы не можете статически связать OpenGL с вашей программой.Здесь используется динамическое связывание, позволяющее оптимизировать API для всех платформ.

Еще один вопрос, который еще не обсуждался, — исправление ошибок в библиотеке.

При статической компоновке вам придется не только пересобирать библиотеку, но и повторно компоновать и перераспределять исполняемый файл.Если библиотека используется только в одном исполняемом файле, это может не быть проблемой.Но чем больше исполняемых файлов необходимо перекомпоновать и перераспределить, тем сильнее становится проблема.

При динамическом связывании вы просто перестраиваете и распространяете динамическую библиотеку, и все готово.

статическая компоновка дает вам только один exe-файл, поэтому для внесения изменений вам необходимо перекомпилировать всю программу.В то время как при динамическом связывании вам нужно вносить изменения только в dll, и когда вы запускаете свой exe, изменения будут подхвачены во время выполнения. С помощью динамического связывания проще предоставлять обновления и исправления ошибок (например:окна).

Существует огромное количество систем, в которых экстремальный уровень статического связывания может оказать огромное положительное влияние на приложения и производительность системы.

Я имею в виду то, что часто называют «встроенными системами», многие из которых сейчас все чаще используют операционные системы общего назначения, и эти системы используются для всего, что только можно себе представить.

Чрезвычайно распространенным примером являются устройства, использующие системы GNU/Linux, использующие Бизибокс.Я довел это до крайности с NetBSD путем создания загрузочного образа системы i386 (32-разрядного), который включает в себя как ядро, так и его корневую файловую систему, причем последняя содержит одну статически связанную (путем crunchgen) двоичный файл с жесткими ссылками на все программы, которые сам содержит все (ну наконец-то насчитали 274) стандартных полнофункциональных системных программ (большинство, кроме тулчейна), а это меньше 20 мегабайт (и, вероятно, очень комфортно работает в системе с объемом памяти всего 64 МБ (даже с несжатой корневой файловой системой и полностью в оперативной памяти), хотя мне не удалось найти такую ​​маленькую систему, на которой можно было бы ее протестировать).

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

Однако это еще не вся история.Я также обычно собираю и использую установки операционной системы NetBSD для своих полных систем разработки, статически связывая все двоичные файлы.Несмотря на то, что это требует значительно больше дискового пространства (всего ~6,6 ГБ для x86_64 со всем, включая набор инструментов и статическую привязку X11) (особенно если для всех программ сохраняются полные таблицы символов отладки, еще ~2,5 ГБ), результат все равно В целом работает быстрее, а для некоторых задач даже использует меньше памяти, чем типичная динамически подключаемая система, которая предполагает совместное использование кодовых страниц библиотеки.Диск дешев (даже быстрый диск), и память для кэширования часто используемых дисковых файлов также относительно дешева, но циклы ЦП на самом деле не так дороги, и платить за них не стоит. ld.so стоимость запуска для каждого запускаемого процесса каждый время его запуска отнимет часы и часы циклов ЦП от задач, требующих запуска многих процессов, особенно когда одни и те же программы используются снова и снова, например, компиляторы в системе разработки.Статически связанные программы инструментальной цепочки могут сократить время сборки всей ОС для моих систем с использованием нескольких архитектур. часы.Мне еще предстоит встроить набор инструментов в свой единственный crunchgen'ed в двоичном виде, но я подозреваю, что когда я это сделаю, будет сэкономлено больше часов времени сборки из-за выигрыша в кэше ЦП.

Статическая компоновка объединяет файлы, необходимые программе, в один исполняемый файл.

Динамическое связывание — это то, что вы считаете обычным: оно создает исполняемый файл, для которого по-прежнему требуется, чтобы библиотеки DLL и тому подобное находились в одном каталоге (или библиотеки DLL могут находиться в системной папке).

(DLL = динамическая ссылка библиотека)

Динамически подключаемые исполняемые файлы компилируются быстрее и не так требовательны к ресурсам.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top