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

400

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

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

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

Eloff
источник
59
«Со статической связью компилятор может оптимизировать .. код библиотеки», но только если он тоже скомпилирует! Если вы просто ссылаетесь на предварительно скомпилированные объектные файлы, ваш компилятор не сможет оптимизировать их.
3
Если это правда, то вы правы, но есть некоторый вопрос относительно того, насколько это верно для современных компиляторов, если кто-то может проверить это так или иначе, это было бы здорово.
Eloff
5
С компилятором, компилируемым в нативный код (как большинство компиляторов C / C ++), больше нет шансов для оптимизации кода. Если код скомпилирован на некотором промежуточном языке (например .Net IL), JIT-компилятор вызывается, когда библиотека загружается, чтобы скомпилировать его в собственный код. Эта окончательная компиляция может со временем улучшаться по мере развития JIT-компилятора.
Таридон
3
@Eloff: VS2008 делает именно это с включенным LTCG. (Файлы lib становятся огромными, хотя ...) Я поиграл с этим, и для тех, кто интересуется "что может сделать мой компилятор", это просто удивительно.
peterchen

Ответы:

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

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


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

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

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

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

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

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

dmckee --- котенок экс-модератора
источник
24
Потребление ресурсов - это, в основном, пространство кода, которое с течением времени вызывает все меньше беспокойства. Если 500 КБ библиотеки распределены между 5 процессами, это экономия 2 МБ, что составляет менее 0,1% от 3 ГБ ОЗУ.
Марк Рэнсом
3
Если библиотека также использует одно и то же виртуальное отображение (один и тот же физический и виртуальный адрес во всех процессах), разве динамическая ссылка также не сохраняет слоты TLB в MMU процессора?
Zan Lynx
6
Кроме того, динамическая ссылка позволяет легко обновлять код библиотеки с ошибками с помощью лучших версий.
Zan Lynx
89
@Zan Это также позволяет легко добавлять код ошибки в рабочую версию.
6
«Плагины всегда требуют динамического связывания». Это неверно Некоторые модели плагинов, такие как Apple AudioUnits, могут запускать плагин в отдельном процессе и использовать IPC. Это более безопасная альтернатива динамической компоновке для плагинов (плагин не может привести к сбою хоста). Предложите обновить ответ на «Плагины могут требовать динамического связывания» или аналогичные.
Тейлор
68

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.

Лотар
источник
4
Мне нравится этот ответ, потому что он был единственным, чтобы ответить на вопросы, которые я поднял в этом вопросе.
Eloff
Было бы интересно иметь ссылки на эти технические подробности DLL и сравнение между различными операционными системами.
UncleZeiv
Кажется, хорошо, но скорость процессора определенно не достигла своего предела.
Aidiakapi
67

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

Марк Рэнсом
источник
17
Пока конечный пользователь может ссылаться на код LGPL (например, потому что вы предоставляете исходный код или скомпилированные объектные файлы вместе с программным обеспечением), статическое связывание - это хорошо . Кроме того, если ваше программное обеспечение предназначено для внутреннего использования (т. Е. Для использования только в вашей организации и не распространяется), вы можете статически связываться. Это относится, например, к серверному программному обеспечению, где сервер не распространяется.
JBentley
3
Не понимаю. Не могли бы вы дать мне больше источников (или подробнее), чтобы оценить то, что вы написали?
Баская
4
@ Торн см. Раздел лицензии LGPL 4.d + e . Вам нужно либо распространять в форме, которая требует от пользователя сделать ссылку, либо распространять общую (динамическую) библиотеку.
Марк Рэнсом
46

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

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

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

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

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

Роб Уэллс
источник
1
очень хорошая мысль, я пытался сделать это недавно с некоторым кодом, который у меня есть на работе, но компиляция всего статически оказалась удивительно раздражающей, и я просто сдался
UncleZeiv
21

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

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

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

Я пришел к выводу, что я использовал статические ссылки исключены:

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

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

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

AProgrammer
источник
1
Некоторые ОС для процессоров, отличных от PIC или дорогих PIC, подготавливают динамические библиотеки для загрузки по определенному адресу в памяти, и, если они могут это сделать, они просто отображают копию библиотеки на каждый процесс, связанный с ней. Это значительно снижает накладные расходы PIC. По крайней мере, OS X и некоторые дистрибутивы Linux делают это, я не уверен насчет Windows.
Эндрю МакГрегор
Спасибо, Эндрю, я не знал, что некоторые дистрибутивы Linux используют это. У вас есть ссылка, которой я могу следовать, или ключевое слово, которое я могу найти, чтобы узнать больше? (FWIW Я слышал, что Windows делает вариант этого, но Windows слишком далеко за пределами моей компетенции, чтобы я упомянул об этом).
AProgrammer
Я думаю, что ключевое слово, которое вы ищете, это «prelink» - оно готовит библиотеку для быстрой загрузки по определенному адресу, чтобы ускорить запуск программы.
Blaisorblade
20

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

н.у.к.
источник
3
+1 за ссылку на Dropper's DSO howto, которую должны прочитать все, кто делает библиотеки в Linux.
2010
10

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

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

Джонатан Леффлер
источник
1
Суть в том, что LD_LIBRARY_PATHэто не является препятствием для использования общих библиотек, по крайней мере, в GNU / Linux. Например, если вы поместите общие библиотеки в каталог ../lib/относительно файла программы, то с помощью цепочки инструментов GNU опция компоновщика -rpath $ORIGIN/../libбудет указывать на поиск библиотеки из этого относительного местоположения. Затем вы можете легко переместить приложение вместе со всеми связанными общими библиотеками. Используя этот трюк, можно без проблем иметь несколько версий приложения и библиотек (при условии, что они связаны, если нет, вы можете использовать символические ссылки).
FooF
> для процессов с привилегиями root. Я думаю, что вы говорите о программах setuid, запускаемых пользователями без прав root - иначе это не имеет смысла. И бинарный файл setuid с библиотеками в нестандартных местах странный - но поскольку только root может устанавливать эти программы, он также может редактировать /etc/ld.so.confдля этого случая.
Blaisorblade
10

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

Ганс Пассант
источник
1
На самом деле я не сравнивал разницу в скорости компиляции, но я бы динамически связывал, если бы она была значительно быстрее. Boost делает достаточно плохих вещей для моего времени компиляции.
Eloff
9

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

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

Arne
источник
8

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

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

Томас Мэтьюз
источник
1
Важно отметить, что существует целый ряд контрмер, чтобы избежать DLL Hell.
ocodo 26.12.12
5

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

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

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

R Самуэль Клатчко
источник
2

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

Говардхан Мурали
источник
2

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

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

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

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

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

Грег А. Вудс
источник
2

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

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

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

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

Nykal
источник
0

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

Минусы:

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

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

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

Минусы:

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