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

342

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

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

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

  • 54
    «Со статическим связыванием компилятор может оптимизировать .. код библиотеки», но только если он тоже это компилирует! Если вы просто ссылаетесь на предварительно скомпилированные объектные файлы, ваш компилятор не сможет оптимизировать их.
  • 2
    Если это правда, то вы правы, но есть некоторый вопрос относительно того, насколько это верно для современных компиляторов, если кто-то может проверить это так или иначе, это было бы здорово.
Показать ещё 2 комментария
Теги:
performance
dynamic-linking
static-linking

15 ответов

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

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


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

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

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

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

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

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

  • 19
    Потребление ресурсов - это, в основном, пространство кода, которое с течением времени вызывает все меньше беспокойства. Если 500 КБ библиотеки распределены между 5 процессами, это экономия 2 МБ, что составляет менее 0,1% от 3 ГБ ОЗУ.
  • 2
    Если библиотека также использует одно и то же виртуальное отображение (один и тот же физический и виртуальный адрес во всех процессах), разве динамическая ссылка также не сохраняет слоты TLB в MMU процессора?
Показать ещё 22 комментария
65

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

  • 14
    Если конечный пользователь может ссылаться на код LGPL (например, потому что вы предоставляете исходный код или скомпилированные объектные файлы вместе с программным обеспечением), статическое связывание - это хорошо . Кроме того, если ваше программное обеспечение предназначено для внутреннего использования (т. Е. Для использования только в вашей организации и не распространяется), вы можете статически связываться. Это относится, например, к серверному программному обеспечению, где сервер не распространяется.
  • 2
    Не понимаю. Не могли бы вы дать мне больше источников (или подробней), чтобы оценить то, что вы написали?
Показать ещё 1 комментарий
61

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.

  • 4
    Мне нравится этот ответ, потому что он был единственным, чтобы ответить на вопросы, которые я поднял в этом вопросе.
  • 0
    Было бы интересно иметь ссылки на эти технические подробности DLL и сравнение между различными операционными системами.
Показать ещё 1 комментарий
40

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 1
    Некоторые ОС для процессоров, отличных от PIC или дорогостоящих PIC, подготавливают динамические библиотеки для загрузки по определенному адресу в памяти, и, если они могут это сделать, они просто отображают копию библиотеки на каждый процесс, связанный с ней. Это значительно снижает накладные расходы PIC. По крайней мере OS X и некоторые дистрибутивы Linux делают это, я не уверен насчет Windows.
  • 0
    Спасибо, Эндрю, я не знал, что некоторые дистрибутивы Linux используют это. У вас есть ссылка, которой я могу следовать, или ключевое слово, которое я могу найти, чтобы узнать больше? (FWIW Я слышал, что Windows делает вариант этого, но Windows слишком далеко за пределами моей компетенции, чтобы я упомянул об этом).
Показать ещё 1 комментарий
19

В этом разделе подробно рассказывается об общих библиотеках Linux и производительности.

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

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

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

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

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

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

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

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

7

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

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

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

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

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

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

3

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

1

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

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

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

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

1

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

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

Чрезвычайно распространенным примером являются устройства, использующие системы 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, но я подозреваю, что когда я это сделаю, будет больше времени на сборку, сохраненное из-за выигрыша для кэша процессора.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню