静态链接与动态链接

在某些情况下,是否有任何令人信服的性能原因来选择通过动态链接进行静态链接,反之亦然? 我听过或读过以下内容,但是我对这个问题的认识还不够充分。

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

2)(1)如果使用分析编译器使用配置文件数据来优化程序热路径,因为使用静态链接,编译器可以优化代码和库代码。 通过动态链接,只有你的代码可以被优化。 如果大部分时间用于运行库代码,这可能会产生很大的差异。 否则,(1)仍然适用。

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

一些编辑在评论和其他答案中包含非常相关的建议。 我想指出的是,你打破这种方式很大程度上取决于你计划运行的环境。最小的嵌入式系统可能没有足够的资源来支持动态链接。 稍微大一点的小系统可能会支持链接,因为它们的内存足够小,使得从动态链接中节省的RAM非常有吸引力。 正如马克指出的,成熟的消费者个人电脑拥有巨大的资源,而且您可能会让便利的问题驱使您思考这个问题。


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

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

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

另一个问题:加载时间。 你在某个时候支付了加载成本。 当你支付这笔费用取决于操作系统如何工作以及你使用的是什么链接。 也许你宁愿拖延付款,直到你知道你需要它。

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

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

动态链接是满足LGPL等许可要求的唯一实用方法。

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以上的功能(从发电机)一直是一个问题,需要嵌入式蹦床。

但是在像Linux这样流行的x86操作系统上,如果SO / DLL文件不是使用gcc开关-fpic (强制使用间接跳转表)生成的,则不需要关心。 因为如果你不这样做,代码就像正常的链接器那样被修复。 但是,这样做会使代码段不可共享,并且需要将代码从磁盘完全映射到内存中,并在使用之前将其全部触及(清空大部分缓存,打击TLB)等等。当这被认为是慢…太慢。

所以你不会有任何好处了。

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

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

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

做一个静态链接构建的一个原因是验证你已经完全关闭了可执行文件,也就是所有的符号引用都被正确的解析了。

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

这通常发生在深度坐在共享库内的符号有拼写错误名称,因此不会静态链接。 动态链接器不能完全解决所有的符号,不管是使用深度优先还是宽度优先评估,所以你可以完成一个没有完全关闭的动态链接可执行文件。

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

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

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

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

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

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

如果想使用“简单补丁”,我认为图书馆必须像上面的大型图书馆那样进行管理:它们必须几乎是独立的,并且必须是一个不能修改的ABI。

这里详细讨论Linux上的共享库和性能impliaction。

在类Unix系统上,动态链接可能会使“root”使用应用程序的共享库安装在不正确的位置。 这是因为动态链接器通常不会注意LD_LIBRARY_PATH或其具有root权限的进程的等价物。 有时候,静态链接节省了一天的时间。

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

这很简单,真的。 当你修改你的源代码时,你想等待10分钟还是20秒? 二十秒钟是我能忍受的。 除此之外,我要么拿到我们的剑,要么开始考虑如何使用单独的编译将它带回到舒适区。

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

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

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

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

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

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

通过动态链接,您只需重新生成并重新分配动态库即可完成。

静态链接只给你一个exe,为了做一个改变你需要重新编译你的整个程序。 而在动态链接,你只需要改变的DLL,当你运行你的EXE,这些改变将在运行时拾取。它更容易提供动态链接更新和错误修复(如:窗口)。

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

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

一个非常常见的例子是使用Busybox的 GNU / Linux系统的设备。 我已经把这个与NetBSD通过构建一个可引导的i386(32位)系统映像,其中包括一个内核和它的根文件系统,后者包含一个单一的静态链接(通过crunchgen )与硬链接到所有程序本身包含标准全功能系统程序(除了工具链以外的所有程序)(最后计数为274),并且它的大小不到20 字节(并且可能在只有系统运行非常舒适64MB的内存(甚至在未压缩的根文件系统下,完全在内存中),尽管我一直无法找到一个非常小的内存来测试它)。

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

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

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

动态链接是你认为的通常的东西,它使一个可执行文件仍然需要DLLs,并且在同一个目录中(或者DLL可以在系统文件夹中)。

(DLL = 动态链接库)

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

Interesting Posts