静态链接与动态链接


398

在某些情况下,是否有任何令人信服的性能原因选择静态链接而不是动态链接?我已经听过或阅读了以下内容,但是我对这个主题的了解不足,无法保证其准确性。

1)静态链接和动态链接之间的运行时性能差异通常可以忽略不计。

2)(1)如果使用使用概要文件数据来优化程序热路径的性能分析编译器,则不正确,因为使用静态链接,编译器可以同时优化您的代码和库代码。使用动态链接只能优化您的代码。如果大部分时间都花在运行库代码上,那将有很大的不同。否则,(1)仍然适用。


59
“通过静态链接,编译器可以优化..库代码”,但前提是必须进行编译!如果仅链接到预编译的目标文件,则编译器将没有机会对其进行优化。

3
如果这是正确的,那么您是对的,但是对于现代编译器的正确性存在一些问题,如果有人可以以一种或另一种方式进行验证,那将是很好的。
Eloff 2010年

5
使用编译器编译为本机代码(就像大多数C / C ++编译器一样),就没有进一步的机会进行代码优化。如果将代码编译为某种中间语言(例如.Net IL),则在加载库时将调用JIT编译器以将其编译为本机代码。随着JIT编译器的发展,最终编译会随着时间的推移变得越来越好。
Tarydon'1

3
@Eloff:VS2008在启用LTCG的情况下完全可以做到这一点。(尽管lib文件变得很大。)我一直在玩弄它,而对于那些对“我的编译器能为我做些什么”感兴趣的人来说,这简直就是惊人。
peterchen 2010年

Answers:


347
  • 动态链接可以减少总资源消耗(如果多个进程共享同一个库(当然,包括“相同”中的版本))。我相信这是促使它在大多数环境中都存在的论点。这里的“资源”包括磁盘空间,RAM和缓存空间。当然,如果动态链接程序的灵活性不足,则存在DLL hell的风险。
  • 动态链接意味着可以传播错误修复和库升级,以改进您的产品而无需运送任何物品。
  • 插件始终要求进行动态链接。
  • 静态链接意味着您可以知道代码将在非常有限的环境中运行(在启动过程中或在救援模式下)。
  • 静态链接可以使二进制代码更易于分发到不同的用户环境(以发送更大,更占用资源的程序为代价)。
  • 静态链接可能会稍微加快启动时间,但这在某种程度上取决于程序的大小和复杂性以及操作系统的加载策略的详细信息。

进行一些编辑以在评论和其他答案中包含非常相关的建议。我想指出的是,您打破这种情况的方式在很大程度上取决于您计划在何种环境下运行。最小的嵌入式系统可能没有足够的资源来支持动态链接。稍大的小型系统可能会很好地支持动态链接,因为它们的内存足够小,可以使动态链接节省的RAM非常吸引人。正如Mark所指出的那样,功能强大的消费类PC拥有大量资源,您可能会让便利问题驱使您对此事进行思考。


解决性能和效率问题:取决于

传统上,动态库需要某种胶合层,这通常意味着在函数寻址中需要双重调度或额外的间接层,并且可能花费一点速度(但是函数调用时间实际上是您运行时间的很大一部分吗?)。

但是,如果您运行的多个进程都多次调用同一个库,则相对于使用静态链接而言,使用动态链接时,最终可以节省缓存行(从而赢得运行性能)。(除非现代操作系统足够聪明,以至于可以注意到静态链接的二进制文件中的相同段。似乎很难,有人知道吗?)

另一个问题:加载时间。您需要在某些时候支付加载费用。何时付款,取决于操作系统的工作方式以及所使用的链接。也许您宁愿推迟付款,直到您知道自己需要它为止。

注意,传统上,静态-动态-动态链接不是优化问题,因为它们都涉及到目标文件的单独编译。但是,这不是必需的:编译器原则上可以最初将“静态库”“编译”为摘要的AST形式,然后通过将这些AST添加到为主代码生成的AST中来“链接”它们,从而实现全局优化。我使用的系统都没有做到这一点,因此我无法评论它的运行情况。

回答性能问题的方法始终是通过测试(并尽可能使用测试环境,就像部署环境一样)。


24
资源消耗基本上是代码空间,随着时间的流逝,它变得越来越少。如果在5个进程之间共享500K的库,则可节省2MB,不到3GB RAM的0.1%。
Mark Ransom'1

3
如果该库还共享相同的虚拟映射(在所有进程中具有相同的物理和虚拟地址),那么动态链接是否也将TLB插槽保存在处理器的MMU中?
Zan Lynx'1

6
动态链接还使您可以轻松地用更好的版本更新错误的库代码。
Zan Lynx'1

89
@Zan还可以轻松地将错误代码添加到工作版本中。

6
“插件总是要求动态链接。” 不对 某些插件模型(例如Apple的AudioUnits)可以在单独的过程中运行插件并使用IPC。这是动态链接插件的更安全选择(插件不会使主机崩溃)。建议将答案更新为“插件可能需要动态链接”或类似内容。
泰勒

68

1)基于以下事实:调用DLL函数始终使用额外的间接跳转。如今,这通常可以忽略不计。在DLL内部,i386 CPU的开销更大,因为它们无法生成位置无关的代码。在amd64上,跳转可以相对于程序计数器,因此这是一个巨大的改进。

2)这是正确的。通过性能分析进行优化,通常可以赢得大约10-15%的性能。现在,CPU速度已达到极限,可能值得这样做。

我要补充一点:(3)链接器可以以更高效的缓存分组方式安排功能,从而使昂贵的缓存级别未命中率降至最低。它还可能特别影响应用程序的启动时间(基于我在Sun C ++编译器中看到的结果)

并且不要忘记,使用DLL不能执行无效代码消除。取决于语言,DLL代码也可能不是最佳的。虚函数始终是虚函数,因为编译器不知道客户端是否覆盖它。

由于这些原因,如果根本不需要DLL,请使用静态编译。

编辑(回答评论,按用户下划线)

这是有关位置独立代码问题的好资源,http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/

如前所述,x86的15位跳转范围以及无条件的跳转和调用都不具有AFAIK。这就是为什么(来自发电机的)功能超过32K一直是一个问题,需要嵌入式蹦床。

但是在像Linux这样的流行x86操作系统上,您不必担心.so / DLL文件是否不是随gcc开关生成的-fpic(强制使用间接跳转表)。因为如果您不这样做,代码将像普通链接器将其重新定位一样被固定。但是,这样做会使代码段不可共享,并且需要将代码从磁盘到内存的完整映射,并在使用之前将其全部触摸(清空大多数缓存,击中TLB)。等等。当这被认为是缓慢的。

因此,您将不再有任何好处。

我不记得什么操作系统(Solaris或FreeBSD的)给我的问题,我的Unix编译系统,因为我只是不这样做的,不知道为什么它坠毁,直到应用我-fPICgcc


4
我喜欢这个答案,因为它是解决我在问题中提出的观点的唯一答案。
Eloff 2010年

拥有这些DLL技术的参考以及不同操作系统之间的比较将是很有趣的。
UncleZeiv

看起来不错,但是CPU速度绝对没有达到极限。
Aidiakapi 2015年

67

动态链接是满足某些许可证要求(例如LGPL)的唯一实用方法。


17
只要最终用户可以重新链接到LGPL的代码(例如,因为您随软件一起提供了源代码或编译的目标文件),那么静态链接就可以了。另外,如果您的软件仅供内部使用(即仅在组织内部使用,而不是分布式的),则可以进行静态链接。这将适用于例如服务器软件,其中服务器不分布。
JBentley

3
不明白 您能给我更多资料(或详细说明)以欣赏您写的内容吗?
Baskaya

4
@Thorn参见LGPL许可条款4.d + e。您或者需要以要求用户进行链接的形式分发,或者分发共享的(动态)库。
Mark Ransom

46

我同意dnmckee提到的要点,此外:

  • 静态链接的应用程序可能更易于部署,因为很少或没有其他文件依赖项(.dll / .so),当它们丢失或安装在错误的位置时,它们可能会引起问题。

6
值得注意的是,来自Google的Go编译器会出于此原因而静态编译二进制文件。
2014年

34

进行静态链接生成的原因之一是要验证您完全关闭了可执行文件,即,正确解析了所有符号引用。

作为使用持续集成构建和测试的大型系统的一部分,夜间回归测试使用可执行文件的静态链接版本运行。有时,即使动态链接的可执行文件可以成功链接,我们也会看到符号无法解析并且静态链接将失败。

当共享库中深处的符号具有拼写错误的名称,因此不会静态链接时,通常会发生这种情况。无论使用深度优先评估还是宽度优先评估,动态链接程序都不会完全解析所有符号,因此您可以完成一个没有完全关闭的动态链接可执行文件。


1
非常好的一点,我最近一直在尝试使用一些我正在使用的代码来做到这一点,但是静态地编译所有内容却令人惊讶地令人讨厌,我只是放弃了
UncleZeiv 2010年

21

1 /我去过的项目中,对动态链接与静态链接进行了基准测试,并且确定的差异还不足以切换到动态链接(我不是测试的一部分,我只知道结论)

2 /动态链接通常与PIC(位置独立代码,不需要根据其加载地址进行修改的代码)相关联。根据架构的不同,PIC可能会带来另一个速度下降,但是为了获得在两个可执行文件之间共享动态链接库的好处(如果操作系统使用加载地址的随机性作为安全措施,甚至是同一可执行文件的两个进程)共享,PIC是必需的。我不确定是否所有操作系统都可以将这两个概念分开,但是Solaris和Linux可以做到,HP-UX也可以做到ISTR。

3 /我参与过其他项目,这些项目使用动态链接实现“简易补丁”功能。但是,这种“简单补丁”使小补丁的分发更加容易,而复杂补丁的分发成为了噩梦。由于错误的版本是令牌,因此我们常常不得不进行所有工作,并且不得不在客户现场跟踪问题,最终导致失败。

我的结论是我使用了静态链接,但:

  • 适用于依赖动态链接的插件之类的东西

  • 共享很重要时(多个进程同时使用的大型库,例如C / C ++运行时,GUI库等),这些库通常是独立管理的,并且必须为其严格定义ABI)

如果要使用“简易补丁程序”,我认为必须像上面的大型库一样对库进行管理:它们必须几乎独立,并且已定义的ABI不能通过修复程序进行更改。


1
一些用于非PIC或价格昂贵的PIC处理器的OS将准备动态库以将其加载到内存中的特定地址,如果可以这样做,则它们仅将库的副本映射到与其链接的每个进程。这大大减少了PIC的开销。至少OS X和某些Linux发行版可以这样做,但我不确定Windows。
安德鲁·麦格雷戈

谢谢安德鲁,我不知道某些Linux发行版使用了这个功能。您是否有可以参考的参考文献或可以搜索以了解更多信息的关键词?(FWIW,我听说Windows正在这样做,但是Windows超出了我的能力范围,我无法提及)。
AProgrammer 2010年

我认为您要查找的关键字是“ prelink”-它准备将库快速加载到某个地址,以使程序启动更快。
Blaisorblade

20

本章详细讨论了Linux上的共享库及其性能影响。


3
+1链接到Drepper的DSO howto,在Linux上制作库的每个人都应该阅读。
janneb 2010年

10

在类似Unix的系统上,动态链接可能会使“ root”难以使用在偏僻位置安装了共享库的应用程序。这是因为动态链接程序通常不会注意具有根特权的进程的LD_LIBRARY_PATH或其等效项。有时,静态链接可以节省一天的时间。

或者,安装过程必须找到这些库,但这会使软件的多个版本难以在计算机上共存。


1
关于这一点LD_LIBRARY_PATH并不是使用共享库的障碍,至少在GNU / Linux中没有。例如,如果将共享库放在../lib/相对于程序文件的目录中,则使用GNU工具链,链接器选项-rpath $ORIGIN/../lib将指定从该相对位置搜索库。然后,您可以轻松地将应用程序与所有关联的共享库一起重新定位。使用此技巧,就可以同时拥有多个版本的应用程序和库(假设它们是相关的,如果没有关系,则可以使用符号链接)。
FooF 2012年

>用于具有root特权的进程。我认为您正在谈论的是由非root用户运行的setuid程序-否则,这没有任何意义。在非标准位置带有库的setuid二进制文件很奇怪-但由于只有root才能安装这些程序,因此他也可以/etc/ld.so.conf针对这种情况进行编辑。
Blaisorblade

10

确实很简单。当您更改源代码时,是否要等待10分钟才能构建代码或20秒?我只能忍受20秒。除此之外,我要么剑拔or张,要么开始考虑如何使用单独的编译和链接将其带回舒适区域。


1
我实际上尚未对编译速度的差异进行基准测试,但是如果动态链接的速度明显更快,我会进行动态链接。Boost对我的编译时间做了很多坏事情。
Eloff 2010年

9

动态链接的最佳示例是库依赖于所使用的硬件。在远古时代,C数学库被认为是动态的,因此每个平台都可以使用所有处理器功能对其进行优化。

一个更好的例子可能是OpenGL。OpenGl是一种由AMD和NVidia以不同方式实现的API。而且由于硬件不同,您将无法在AMD卡上使用NVidia实现。因此,您不能将OpenGL静态链接到程序中。这里使用动态链接使API针对所有平台进行优化。


8

动态链接需要额外的时间,操作系统才能找到并加载动态库。使用静态链接,所有内容都在一起,并且一次性加载到内存中。

另请参阅DLL Hell。在这种情况下,操作系统加载的DLL不是您的应用程序随附的DLL,也不是您的应用程序期望的版本。


1
重要的是要注意,有很多对策可以避免DLL Hell。
ocodo 2012年

5

尚未讨论的另一个问题是修复库中的错误。

使用静态链接,您不仅必须重建库,还必须重新链接并重新分发可执行文件。如果该库仅在一个可执行文件中使用,则可能不是问题。但是,需要重新链接和重新分发的可执行文件越多,痛苦就越大。

使用动态链接,您只需重建并重新分发动态库即可。


2

静态链接仅给您一个exe,以便进行更改,您需要重新编译整个程序。在动态链接中,您只需要对dll进行更改,而在运行exe时,这些更改将在运行时获取。通过动态链接(例如Windows)更容易提供更新和错误修复。


2

在数量众多且数量不断增加的系统中,极端级别的静态链接可能会对应用程序和系统性能产生巨大的积极影响。

我指的是通常被称为“嵌入式系统”的系统,其中许多现在越来越多地使用通用操作系统,并且这些系统用于所有可以想象的事情。

一个非常常见的示例是使用Busybox的使用GNU / Linux系统的设备。我通过构建可引导的i386(32位)系统映像(包括内核和其根文件系统)将NetBSD发挥到了极致,后者包含单个静态链接的(由crunchgen)二进制文件以及与之的硬链接。所有本身包含所有标准功能系统程序的程序(最后计数为274)(除工具链以外的大多数程序),并且其大小小于20 字节(并且可能在只有64MB的系统中非常舒适地运行内存(即使根文件系统未压缩且完全在RAM中),尽管我无法找到这么小的内存来进行测试。

它已经在前面的帖子已经提到,静态链接二进制的启动时间更快(它可以是一个很大更快),但是这是图片中的一部分,尤其是当所有的对象代码被链接到同一个文件,尤其是在操作系统支持直接从可执行文件进行按需分页代码时。在这种理想情况下,程序的启动时间实际上可以忽略不计,因为几乎所有代码页都已经在内存中并且被外壳程序(以及init可能正在运行的任何其他后台进程)使用,即使请求的程序没有自引导以来一直运行,因为可能只需要加载一页内存即可满足程序的运行时要求。

但是,这还不是全部。我通常还通过静态链接所有二进制文件来为我的完整开发系统构建和使用NetBSD操作系统安装。即使这会占用大量磁盘空间(x86_64总共约6.6GB,包括工具链和X11静态链接在内的所有内容)(尤其是如果一个程序对所有程序都保留了完整的调试符号表,而另外还有约2.5GB),结果仍然整体运行速度更快,并且某些任务甚至比声称共享库代码页的典型动态链接系统使用更少的内存。磁盘很便宜(甚至是快速磁盘),用于缓存常用磁盘文件的内存也相对便宜,但是CPU周期确实不是,并且ld.so为每个启动的进程支付启动成本都要花启动所需的时间与需要启动许多进程的任务相比,将花费数小时的CPU周期,尤其是当一遍又一遍地使用相同的程序(例如开发系统上的编译器)时。静态链接的工具链程序可以将我的系统的全OS多体系结构构建时间减少数小时。我还没有将工具链构建到我的单个crunchgen'ed二进制文件中,但是我怀疑这样做的时候,由于赢得了CPU缓存,因此可以节省更多的构建时间。


2

静态链接将程序需要的文件包含在单个可执行文件中。

动态链接是您通常认为的那样,它使可执行文件仍需要DLL,并且仍位于同一目录中(或者DLL可能位于系统文件夹中)。

(DLL = 动态链接库)

动态链接的可执行文件的编译速度更快,而且资源不多。


0

Static linking 是将链接内容复制到主二进制文件并成为单个二进制文件时的编译过程。

缺点:

  • 编译时间更长
  • 输出二进制更大

Dynamic linking是运行时加载链接内容的过程。该技术允许:

  • 升级链接的二进制文件,而无需重新编译可提高ABI稳定性的主要二进制文件[关于]
  • 有一个共享副本

缺点:

  • 开始时间较慢(应复制链接的内容)
  • 链接器错误在运行时引发
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.