静态链接与动态链接

Elo*_*off 382 c c++ performance dynamic-linking static-linking

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

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

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

dmc*_*kee 339

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

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


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

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

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

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

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

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

  • @Zan它还可以轻松地将错误代码添加到工作版本中. (84认同)
  • 资源消耗基本上是代码空间,随着时间的推移越来越少.如果在5个进程之间共享500K的库,节省了2MB,这比3GB的RAM少了0.1%. (22认同)
  • 此外,动态链接可以更轻松地使用更好的版本更新错误的库代码. (5认同)
  • "插件总是要求动态链接." 那是不对的.某些插件模型(如Apple的AudioUnits)可以在单独的进程中运行插件并使用IPC.这是插件动态链接的一种更安全的替代方案(插件不会使主机崩溃).建议将答案更新为"插件可能需要动态链接"或类似内容. (5认同)
  • 如果库也共享相同的虚拟映射(所有进程中的物理和虚拟地址相同),那么动态链接是否也会保存处理器MMU中的TLB插槽? (3认同)
  • @MarkRansom:认为资源消耗问题"只是关于RAM"是错误的.缓存空间比RAM更加稀缺,动态链接也减少了它的使用. (2认同)
  • @Zephyr 要加载静态链接程序,您需要加载程序代码和程序使用的库代码子集。要加载动态链接的程序,您需要单独加载程序代码以及尚未驻留在内存中的所有库的全部内容。如果没有一个库已经在内存中,那么后者只能比前者大。现在关于“如果没有图书馆已经存在”的一点是帖子说“可能”而不是“是”的原因。 (2认同)

Lot*_*har 67

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交换机生成SO/DLL文件-fpic(强制使用间接跳转表).因为如果不这样做,代码就像普通链接器一样重新定位.但是在执行此操作时,它会使代码段不可共享,并且需要将代码从磁盘完全映射到内存并在使用它之前将其全部触摸(清空大多数缓存,触及TLB)等.有一段时间当这被认为是缓慢的......太慢了.

所以你不会再有任何好处了.

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

  • 我喜欢这个答案,因为它是唯一一个解决我在问题中提出的观点的人. (4认同)

Mar*_*som 65

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

  • 只要最终用户可以重新链接到LGPL代码(例如,因为您使用软件提供源代码或编译目标文件),[然后静态链接就可以了](http://stackoverflow.com/a/一百二十二万七千四百六十九分之一千〇一十七万九千一百八十一).此外,如果您的软件供内部使用(即仅在您的组织内使用,而不是分发),则可以静态链接.这适用于服务器软件,其中服务器未分发. (17认同)
  • @Thorn参见[LGPL许可证部分4.d + e](http://www.gnu.org/copyleft/lesser.html).您需要以需要用户进行链接的形式分发,或者分发共享(动态)库. (3认同)
  • 不明白.你能不能给我更多的资料(或更多详细信息)来欣赏你写的东西? (2认同)

sta*_*ica 44

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

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

  • 值得注意的是,谷歌的Go编译器将*仅*静态编译二进制文件,主要是因为这个原因. (6认同)

Rob*_*lls 32

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

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

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


APr*_*mer 21

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

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

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

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

  • 对于像依赖动态链接的插件这样的东西

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

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


nos*_*nos 20

这将详细讨论Linux上的共享库和性能冲击.

  • +1链接到Drepper的DSO howto,每个人在Linux上创建库都应该阅读. (3认同)

Han*_*ant 11

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


Jon*_*ler 10

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

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

  • 关于“LD_LIBRARY_PATH”的问题并不完全是使用共享库的障碍,至少在 GNU/Linux 中不是。例如,如果将共享库放在相对于程序文件的目录“../lib/”中,则使用 GNU 工具链,链接器选项“-rpath $ORIGIN/../lib”将指定从该相对位置搜索库。然后,您可以轻松地重新定位应用程序以及所有关联的共享库。使用这个技巧,拥有多个版本的应用程序和库也没有问题(假设它们是相关的,如果不是,你可以使用符号链接)。 (2认同)

yoA*_*ex5 9

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

缺点:

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

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

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

缺点:

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

【iOS静态vs动态框架】


Arn*_*rne 8

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

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


Tho*_*ews 7

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

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


小智 6

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

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

(DLL =动态链接库)

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


R S*_*hko 5

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

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

通过动态链接,您只需重建和重新分发动态库即可.