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

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

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

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

15 ответов

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

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


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

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

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

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

Обратите внимание, что static-vs - динамическое связывание традиционно не является проблемой оптимизации, потому что они оба включают отдельную компиляцию вплоть до объектных файлов. Однако это не требуется: компилятор может в принципе "скомпилировать" "статические библиотеки" вначале в усвоенную форму 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-битных диапазонов перехода, а не для безусловных переходов и вызовов. Вот почему функции (от генераторов), имеющие более 32K, всегда были проблемой и нуждались во встроенных батутах.

Но на популярных ОС 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++, библиотеки GUI, ... которые часто управляются независимо и для которых строго определен ABI)

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

Это обсуждение очень подробно об общих библиотеках на linux и влиянии на производительность.

Static linking - это процесс во время компиляции, когда связанный контент копируется в основной двоичный файл и становится одним двоичным файлом.

Минусы:

  • время компиляции больше
  • выходной двоичный файл больше

Dynamic linkingэто процесс во время выполнения, когда загружается связанный контент. Эта техника позволяет:

  • обновить связанный двоичный файл без перекомпиляции основного, что увеличивает ABIстабильность [О себе]
  • иметь единственную общую копию

Минусы:

  • время начала медленнее (следует скопировать связанный контент)
  • ошибки компоновщика возникают во время выполнения

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

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

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

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

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

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

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

Еще одна проблема, которая еще не обсуждалась, - исправление ошибок в библиотеке.

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

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

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

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

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

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

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

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

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

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

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

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

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

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