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

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

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

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

Ответ 1

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

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


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

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

Итак, у вас больше не будет никакой пользы.

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

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

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

Ответ 12

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

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

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

Ответ 13

статическая ссылка дает вам только один exe, чтобы внести изменения, необходимые для перекомпиляции всей вашей программы. Если в динамической компоновке вам нужно внести изменения только в DLL, и когда вы запустите exe, изменения будут подхвачены во время выполнения. Легче предоставить обновления и исправления ошибок путем динамической компоновки (например: windows).

Ответ 14

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

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

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

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

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

Ответ 15

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

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

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

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