静态链接vs动态链接

在某些情况下,选择静态链接而不是动态链接或反之,是否有令人信服的性能原因?我听过或读过以下内容,但我对这个主题了解不够,无法保证其真实性。

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

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

296245 次浏览
  • 动态链接可以减少总资源消耗(如果多个进程共享同一个库(当然包括"the same"中的版本))。我相信这就是它在大多数环境中存在的理由。这里“resources"包括磁盘空间、RAM和缓存空间。当然,如果你的动态链接器不够灵活,就有DLL地狱的风险。
  • 动态链接意味着错误修复和升级到库传播来改进你的产品,而不需要你发布任何东西。
  • 插件总是调用动态链接。
  • 静态链接,意味着你可以知道代码将运行在非常有限的环境(在引导过程的早期,或在救援模式)。
  • 静态链接可以使二进制文件更容易分发适用于不同的用户环境(以发送更大、更消耗资源的程序为代价)。
  • 静态链接可能允许轻微的更快的启动次,但这在一定程度上取决于你的程序的大小和复杂性。

一些编辑在评论和其他回答中包括非常相关的建议。我想指出的是,这种中断方式在很大程度上取决于您计划在何种环境中运行。最小的嵌入式系统可能没有足够的资源来支持动态链接。稍大一些的小型系统可能很好地支持动态链接,因为它们的内存足够小,使得动态链接节省的RAM非常有吸引力。成熟的消费pc,如马克的笔记,拥有巨大的资源,你可能会让便利性问题驱动你对这个问题的思考。


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

通常情况下,动态库需要某种粘合层,这通常意味着双重分派或函数寻址中额外的间接层,并且可能会消耗一点速度(但是函数调用时间实际上是运行时间的很大一部分吗??)

但是,如果您正在运行多个进程,并且它们都经常调用同一个库,那么使用动态链接而不是静态链接时,您最终可以节省缓存行(从而赢得运行性能)。(除非现代操作系统足够智能,能够在静态链接的二进制文件中注意到相同的段。似乎很难,有人知道吗?)

另一个问题:加载时间。你需要支付装货费用。你什么时候支付这个费用取决于操作系统的工作方式以及你使用的链接。也许你宁愿推迟支付,直到你知道你需要它。

注意,静态与动态链接传统上是一个优化问题,因为它们都涉及到对目标文件的单独编译。然而,这不是必需的:编译器原则上可以“编译”;“静态libraries"最初转换为消化的AST形式,并且“;link"通过将这些ast添加到为主代码生成的ast中,从而实现全局优化。我使用的系统都没有做到这一点,所以我不能评论它的工作效果如何。

回答性能问题的方法是通过测试总是(并尽可能使用与部署环境相似的测试环境)。

我同意dnmckee提到的观点,另外:

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

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

执行静态链接构建的一个原因是验证可执行文件是否完全关闭,即所有符号引用都正确解析。

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

这种情况通常发生在共享库中位置较深的符号有拼写错误的名称,因此不能进行静态链接时。无论使用深度优先还是宽度优先求值,动态链接器都不能完全解析所有符号,因此您可以完成一个没有完全闭包的动态链接可执行文件。

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

另外,参见DLL地狱。在这种情况下,操作系统加载的DLL不是应用程序自带的,也不是应用程序期望的版本。

这真的很简单。当您在源代码中进行更改时,您希望等待10分钟或20秒来构建它?我只能忍受20秒。除此之外,我要么拿出剑来,要么开始考虑如何使用单独的编译和链接将其带回舒适区。

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

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

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

使用动态链接,只需重新构建和重新分发动态库,就完成了工作。

1/我曾经参与过一个项目,其中动态链接和静态链接是基准测试,差异并没有小到可以切换到动态链接(我没有参与测试,我只知道结论)

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

3/我在其他项目中使用了动态链接的“简单补丁”功能。但是这个“简单补丁”使得小补丁的发布更加容易,而复杂补丁的发布则成为版本控制的噩梦。因为错误的版本是token,我们经常不得不推送所有内容,并在客户站点跟踪问题。

我的结论是,我使用静态链接除外:

  • 比如依赖于动态链接的插件

  • 当共享是重要的(大型库被多个进程同时使用,如C/ c++运行时,GUI库,…它们通常是独立管理的,ABI对此有严格的定义)

如果有人想使用“简单的补丁”,我认为库必须像上面的大库一样管理:它们必须几乎独立,具有定义好的ABI,不能被修复更改。

在类unix系统上,动态链接会使“root”很难使用安装在偏僻位置的共享库的应用程序。这是因为对于具有根权限的进程,动态连接器通常不会注意LD_LIBRARY_PATH或其等效内容。有时,静态链接可以解决问题。

另外,安装过程必须确定库的位置,但这可能会使计算机上的多个软件版本难以共存。

动态链接的最佳示例是库依赖于所使用的硬件。在古代,C数学库是动态的,这样每个平台都可以使用所有的处理器功能来优化它。

一个更好的例子可能是OpenGL。OpenGl是一个由AMD和NVidia实现的不同的API。你不能在AMD卡上使用NVidia实现,因为硬件是不同的。因此,你不能静态地将OpenGL链接到你的程序中。这里使用了动态链接,以便为所有平台优化API。

静态链接只给你一个单一的exe,为了做出改变,你需要重新编译你的整个程序。而在动态链接中,您只需要对dll进行更改,当您运行exe时,这些更改将在运行时被拾取。通过动态链接(例如:windows)更容易提供更新和错误修复。

在大量且越来越多的系统中,极端的静态链接可以对应用程序和系统性能产生巨大的积极影响。

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

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

在以前的文章中已经提到过,静态链接二进制文件的启动时间更快(可以是很多更快),但这只是一部分,特别是当所有目标代码都链接到同一个文件时,尤其是当操作系统支持直接从可执行文件对代码进行需求分页时。在这种理想的情况下,程序的启动时间字面上的可以忽略不计,因为几乎所有的代码页都已经在内存中并被shell使用(并且init任何其他可能正在运行的后台进程),即使所请求的程序在启动后从未运行过,因为可能只需要加载一页内存来满足程序的运行时要求。

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

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

动态链接是你通常会考虑的,它使一个可执行文件仍然需要dll等在同一个目录(或者dll可以在系统文件夹中)。

(DLL = 动态链接库)

动态链接的可执行文件编译速度更快,资源也不那么繁重。

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

缺点:

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

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

  • 升级链接二进制文件而不重新编译增加ABI稳定性的主二进制文件
  • 是否有单个共享副本

缺点:

  • 开始时间较慢(应复制链接内容)
  • 链接器错误在运行时抛出

[iOS静态vs动态框架]

另一个需要考虑的问题是您在库中实际使用的目标文件(翻译单元)的数量与可用的总数之比。如果一个库是由许多对象文件构建的,但你只使用其中的一些符号,这可能是赞成静态链接的一个论点,因为你只链接你在静态链接时使用的对象(通常),通常不携带未使用的符号。如果使用共享库,则该库包含所有翻译单元,并且可能比您想要或需要的要大得多。