关于c ++:静态链接与动态链接

Static linking vs dynamic linking

在某些情况下,选择静态链接而不是动态链接是否有令人信服的性能原因?我听过或读过下面的文章,但我对这个问题的了解不够,不能保证它的真实性。

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

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


  • 动态链接可以减少总的资源消耗(如果多个进程共享同一个库(当然包括"相同"中的版本))。我相信这正是它在大多数环境中存在的原因。这里的"资源"包括磁盘空间、RAM和缓存空间。当然,如果您的动态链接器不够灵活,则存在DLL地狱的风险。
  • 动态链接意味着对库的错误修复和升级可以传播,从而在不需要您发送任何东西的情况下改进您的产品。
  • 插件总是需要动态链接。
  • 静态链接,意味着您可以知道代码将在非常有限的环境中运行(在启动过程的早期,或在救援模式下)。
  • 静态链接可以使二进制文件更容易分发到不同的用户环境(代价是发送一个更大、更需要资源的程序)。
  • 静态链接可能允许更快的启动时间,但这在某种程度上取决于程序的大小和复杂性以及OSS加载策略的细节。

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

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

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

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

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

注意,静态与动态链接传统上不是一个优化问题,因为它们都涉及到对象文件的单独编译。但是,这不是必需的:原则上,编译器可以先将"静态库"编译为消化的ast形式,然后通过将这些ast添加到为主代码生成的ast中来"链接"它们,从而实现全局优化。我使用的所有系统都没有这样做,所以我无法评论它的工作情况。

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


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


1)基于调用dll函数总是使用额外的间接跳转这一事实。今天,这通常可以忽略不计。在动态链接库中,i386 CPU的开销更大,因为它们不能生成位置无关的代码。在AMD64上,跳转可以相对于程序计数器,所以这是一个巨大的改进。

2)这是正确的。通过分析引导的优化,您通常可以获得大约10-15%的性能。既然CPU速度已经达到了极限,那么做可能是值得的。

我想补充一点:(3)链接器可以将函数安排在一个更高效的缓存分组中,这样就可以将昂贵的缓存级别未命中最小化。它还可能特别影响应用程序的启动时间(基于我在Sun C++编译器上看到的结果)

别忘了,有了动态链接库,就不能执行死代码消除。根据语言的不同,DLL代码也可能不是最佳的。虚拟函数总是虚拟的,因为编译器不知道客户端是否正在覆盖它。

出于这些原因,如果不需要DLL,那么只需使用静态编译。

编辑(按用户下划线回答注释)

以下是关于位置独立代码问题的一个很好的资源http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/

正如所解释的那样,x86没有任何其他15位跳转范围的afaik,也没有无条件跳转和调用。这就是为什么功能(来自发电机)超过32K一直是一个问题,需要嵌入蹦床。

但是在流行的x86操作系统(如Linux)上,您不需要关心是否使用gcc开关-fpic生成so/dll文件(这强制使用间接跳转表)。因为如果不这样做,代码就会像普通的链接器一样被修复。但是这样做会使代码段不可共享,并且需要将代码从磁盘映射到内存中,并在使用之前触摸所有代码(清空大部分缓存,点击TLB)等。有一段时间,这被认为是缓慢的……太慢了。

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

我不记得操作系统(solaris或freebsd)给我的Unix构建系统带来了什么问题,因为我没有这样做,我想知道为什么它会崩溃,直到我把-fpic应用到gcc


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

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


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

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

这种情况通常发生在那些深埋在共享libs中的符号有一个拼写错误的名称,因此不会静态链接的时候。动态链接器不会完全解析所有符号,不管使用深度优先还是宽度优先评估,因此您可以使用没有完全关闭的动态链接可执行文件来完成。


1/我曾参与过一些项目,其中动态链接与静态链接是基准测试的,而且差异还没有确定到足以转换为动态链接的程度(我不是测试的一部分,我只是知道结论)。

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

3/我还参与过其他项目,这些项目使用了"轻松补丁"功能的动态链接。但是这个"简单补丁"使得小补丁的分发变得更加容易,而复杂补丁的分发则是版本控制的噩梦。我们经常不得不推送所有东西,还要跟踪客户站点的问题,因为错误的版本是令牌。

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

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

  • 当共享是重要的(大型图书馆同时使用多个进程,如C/C++运行时,GUI库,…通常是独立管理的,对其严格定义了ABI)

如果有人想使用"简单补丁",我会认为库必须像上面的大型库一样进行管理:它们必须几乎独立于一个定义好的ABI,不能通过修复来更改。


本文将详细讨论Linux上的共享库和性能实现。


这很简单,真的。当您对源代码进行更改时,您想等待10分钟以生成它还是等待20秒?我只能忍受20秒。除此之外,我要么拿出剑来,要么开始思考如何使用单独的编译和链接将它带回到舒适区。


在类Unix的系统上,动态链接可能会使"根"使用安装在偏远位置的共享库的应用程序变得困难。这是因为对于具有根权限的进程,动态链接器通常不会注意ld_library_path或其等效项。有时候,静态链接可以节省一天的时间。

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


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

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


动态链接需要额外的时间让操作系统找到动态库并加载它。通过静态链接,所有东西都在一起,这是对内存的一次性加载。

另外,请参阅dll hell。在这种情况下,OS加载的dll不是应用程序附带的dll,也不是应用程序期望的版本。


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

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

使用动态链接,只需重新构建和重新分布动态库,即可完成。


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


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

我指的是通常被称为"嵌入式系统"的系统,其中许多系统现在越来越多地使用通用操作系统,而这些系统被用于一切可以想象到的事情。

一个非常常见的例子是使用GNU/Linux系统的设备使用busybox。通过构建一个可引导的i386(32位)系统映像,它包括内核和根文件系统,后者包含一个静态链接(由crunchgen二进制文件)和到所有程序的硬链接,这些程序本身包含标准全功能系统程序的所有(最后计数274),我对netbsd进行了极端的处理。(除了工具链之外,它的大小还不到20兆字节(在只有64MB内存的系统中可能运行得非常舒适(即使根文件系统是未压缩的,完全在RAM中),尽管我找不到这么小的内存来测试它。

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

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


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

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

(dll=动态链接库)

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