静态链接与动态链接

时间:2010-01-03 00:06:13

标签: c++ c performance static-linking dynamic-linking

在某些情况下,是否有任何令人信服的性能原因选择动态链接上的静态链接,反之亦然?我已经听过或读过以下内容,但我对这个问题不太了解,以保证其真实性。

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

2)(1)如果使用使用配置文件数据优化程序热路径的配置文件编译器,则不成立,因为通过静态链接,编译器可以优化代码和库代码。通过动态链接,您的代码可以进行优化。如果大部分时间都花在运行库代码上,那么这可能会产生很大的不同。否则,(1)仍然适用。

17 个答案:

答案 0 :(得分:325)

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

一些编辑在评论和其他答案中包含非常相关的建议。我想指出,你打破这种方式的方式很大程度上取决于你计划运行的环境。最小的嵌入式系统可能没有足够的资源来支持动态链接。略大的小型系统可能很好地支持链接,因为它们的内存足够小,可以使动态链接节省的RAM非常有吸引力。正如马克指出的那样,全面的消费者电脑拥有巨大的资源,你可能会让便利问题引发你对这件事的思考。


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

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

但是,如果您正在运行多个进程,这些进程都会大量调用同一个库,那么在使用静态链接相对使用动态链接时,最终可以保存缓存行(从而赢得运行性能)。 (除非现代操作系统足够聪明,能够注意到静态链接二进制文件中的相同段。看起来很难,有人都知道吗?)

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

请注意,静态vs动态链接传统上是一个优化问题,因为它们都涉及单独编译到目标文件。但是,这不是必需的:编译器原则上可以“初始化”将“静态库”“编译”为消化的AST形式,并通过将这些AST添加到为主代码生成的那些来“链接”它们,从而赋予全局优化权限。我使用的系统都没有这样做,所以我不能评论它的工作情况。

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

答案 1 :(得分:64)

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没有AFAIK用于其他任何东西,那么15位跳转范围而不是无条件跳转和调用。这就是为什么具有超过32K的功能(来自发电机)一直是一个问题,需要嵌入式蹦床。

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

所以你再也没有任何好处了。

我不记得OS(Solaris或FreeBSD)给我的Unix构建系统带来了什么问题,因为我没有这样做,并想知道为什么在我将-fPIC应用到gcc之前它崩溃了

答案 2 :(得分:64)

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

答案 3 :(得分:42)

我同意dnmckee提到的观点,加上:

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

答案 4 :(得分:32)

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

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

这通常发生在共享库中深处的符号具有拼写错误的名称,因此不会静态链接。动态链接器不会完全解析所有符号,无论使用深度优先或广度优先评估,因此您可以使用没有完全关闭的动态链接可执行文件完成。

答案 5 :(得分:21)

1 /我一直在进行动态链接与静态链接的基准测试项目,并且差异不够小,无法切换到动态链接(我不是测试的一部分,我只知道结论)< / p>

2 /动态链接通常与PIC相关联(位置无关代码,根据加载地址不需要修改的代码)。取决于体系结构,PIC可能带来另一个减速但是为了获得在两个可执行文件之间共享动态链接库的好处(如果OS使用加载地址的随机化作为安全措施,则甚至是相同可执行文件的两个进程)。我不确定所有操作系统是否允许将这两个概念分开,但是Solaris和Linux确实可以和HP-UX一样使用ISTR。

3 /我一直在使用动态链接进行“简单补丁”功能的其他项目。但是这个“简单的补丁”使得小修补程序的分发变得更容易,并且复杂的版本化噩梦。我们经常最终不得不推动所有事情以及必须跟踪客户站点的问题,因为错误的版本是令牌。

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

  • 用于依赖动态链接的插件

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

如果想要使用“简单补丁”,我认为库必须像上面的大型库一样进行管理:它们必须几乎独立于定义的ABI,不能通过修复来改变。< / p>

答案 6 :(得分:20)

This详细讨论了Linux上的共享库和性能冲击。

答案 7 :(得分:11)

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

答案 8 :(得分:10)

在类Unix系统上,动态链接会让“root”生活变得困难,因为在偏远的位置安装了共享库的应用程序。这是因为动态链接器通常不会关注具有root权限的进程的LD_LIBRARY_PATH或其等价物。有时候,静态链接可以节省时间。

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

答案 9 :(得分:7)

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

另请参阅DLL Hell。这是操作系统加载的DLL不是您的应用程序附带的DLL或您的应用程序所期望的版本的情况。

答案 10 :(得分:7)

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

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

答案 11 :(得分:5)

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

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

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

答案 12 :(得分:4)

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

缺点:

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

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

  • 升级链接二进制文件,而无需重新编译主要二进制文件,从而提高了ABI的稳定性[About]
  • 具有单个共享副本

缺点:

  • 开始时间较慢(应复制链接的内容)
  • 运行时引发链接器错误

答案 13 :(得分:3)

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

答案 14 :(得分:2)

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

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

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

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

然而,这仍然不是全部。我还经常通过静态链接所有二进制文件来为我的完整开发系统构建和使用NetBSD操作系统安装。即使这需要更多的磁盘空间(x86_64总共大约6.6GB,包括工具链和X11静态链接)(特别是如果一个保持完整的调试符号表可用于所有程序另外~2.5GB),结果仍然整体运行速度更快,对于某些任务甚至比典型的动态链接系统使用更少的内存,这些系统声称共享库代码页。磁盘很便宜(甚至是快速磁盘),并且用于缓存经常使用的磁盘文件的内存也相对便宜,但CPU周期实际上并非如此,并为每个启动每个进程支付init启动成本小时减少我的系统的整个OS多架构构建时间。我还没有将工具链构建到我的单ld.so'ed二进制文件中,但我怀疑当我这样做时,由于CPU缓存的胜利,将节省更多的构建时间。

答案 15 :(得分:1)

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

动态链接是您通常会考虑的,它会生成一个仍然需要DLL的可执行文件,并且它们位于同一目录中(或者DLL可能位于系统文件夹中)。

(DLL = 动态链接库)

动态链接的可执行文件编译速度更快,并且不会占用大量资源。

答案 16 :(得分:0)

另一个要考虑的因素是您在库中实际消耗的目标文件(翻译单元)数与可用总数的比较。如果库是由许多目标文件构建的,但是您仅使用其中一些符号,则这可能是支持静态链接的一个论据,因为您仅链接静态链接时使用的对象(通常),而不会t通常会携带未使用的符号。如果使用共享库,则该库包含所有翻译单元,并且可能比您想要或需要的库大得多。