在某些情况下,是否有任何令人信服的性能理由来选择静态链接而不是动态链接,反之亦然?我听过或读过以下内容,但我对这个主题的了解还不够,无法保证其真实性。
1) 静态链接和动态链接在运行时性能上的差异通常可以忽略不计。
2) (1) 如果使用使用配置文件数据优化程序热路径的分析编译器则不正确,因为通过静态链接,编译器可以同时优化您的代码和库代码。使用动态链接只能优化您的代码。如果大部分时间都花在运行库代码上,这会产生很大的不同。否则,(1) 仍然适用。
在某些情况下,是否有任何令人信服的性能理由来选择静态链接而不是动态链接,反之亦然?我听过或读过以下内容,但我对这个主题的了解还不够,无法保证其真实性。
1) 静态链接和动态链接在运行时性能上的差异通常可以忽略不计。
2) (1) 如果使用使用配置文件数据优化程序热路径的分析编译器则不正确,因为通过静态链接,编译器可以同时优化您的代码和库代码。使用动态链接只能优化您的代码。如果大部分时间都花在运行库代码上,这会产生很大的不同。否则,(1) 仍然适用。
一些编辑以在评论和其他答案中包含非常相关的建议。我想指出,你打破这个的方式很大程度上取决于你计划在什么环境中运行。最小的嵌入式系统可能没有足够的资源来支持动态链接。稍微大一点的小系统可能很好地支持动态链接,因为它们的内存足够小,使得动态链接节省的 RAM 非常有吸引力。正如Mark 指出的那样,成熟的消费类 PC 拥有大量资源,您可能会让便利性问题推动您对此事的思考。
解决性能和效率问题:这取决于.
经典地,动态库需要某种粘合层,这通常意味着函数寻址中的双重调度或额外的间接层,并且可能会花费一点速度(但函数调用时间实际上是运行时间的很大一部分吗???)。
但是,如果您运行的多个进程都大量调用同一个库,那么在使用动态链接时相对于使用静态链接,您最终可以节省缓存行(从而赢得运行性能)。(除非现代操作系统足够聪明,可以注意到静态链接二进制文件中的相同段。似乎很难,有人知道吗?)
另一个问题:加载时间。您在某个时候支付装载费用。您何时支付此费用取决于操作系统的工作方式以及您使用的链接。也许你宁愿推迟支付,直到你知道你需要它。
请注意,静态与动态链接传统上不是优化问题,因为它们都涉及单独编译到目标文件。但是,这不是必需的:编译器原则上可以将“静态库”最初“编译”为摘要的 AST 形式,然后通过将这些 AST 添加到为主代码生成的 AST 中来“链接”它们,从而实现全局优化。我使用的系统都没有这样做,所以我无法评论它的工作情况。
回答性能问题的方法始终是通过测试(并尽可能使用像部署环境一样的测试环境)。
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)上,您无需关心 .so/DLL 文件是否不是使用gcc
开关生成的-fpic
(这会强制使用间接跳转表)。因为如果你不这样做,代码就像一个普通的链接器会重新定位它一样被固定。但是在这样做的同时,它使代码段不可共享,并且需要将代码从磁盘完整映射到内存并在它可以使用之前全部接触它(清空大部分缓存,命中 TLB)等等。有一段时间当这被认为很慢时。
因此,您将不再有任何好处。
我不记得是什么操作系统(Solaris 或 FreeBSD)给我的 Unix 构建系统带来了问题,因为我只是没有这样做,并且想知道为什么它在我-fPIC
申请gcc
.
动态链接是满足某些许可要求(例如LGPL )的唯一实用方法。
我同意 dnmckee 提到的观点,另外:
进行静态链接构建的一个原因是验证您是否已完全关闭可执行文件,即所有符号引用都已正确解析。
作为使用持续集成构建和测试的大型系统的一部分,夜间回归测试使用可执行文件的静态链接版本运行。有时,我们会看到符号无法解析,并且即使动态链接的可执行文件成功链接,静态链接也会失败。
这通常发生在共享库中根深蒂固的符号名称拼写错误,因此不会静态链接时。无论使用深度优先还是广度优先评估,动态链接器都不会完全解析所有符号,因此您可以完成一个没有完全关闭的动态链接可执行文件。
1 / 我一直在对动态链接和静态链接进行基准测试的项目中,差异没有确定到足以切换到动态链接(我不是测试的一部分,我只知道结论)
2/动态链接通常与PIC(位置无关代码,不需要根据加载地址修改的代码)相关联。根据架构的不同,PIC 可能会带来另一次减速,但需要在两个可执行文件之间共享动态链接库(如果操作系统使用加载地址的随机化作为安全措施,甚至同一可执行文件的两个进程)。我不确定所有操作系统都允许将这两个概念分开,但是 Solaris 和 Linux 可以做到,而 HP-UX 也可以做到 ISTR。
3/ 我参与过其他使用动态链接来实现“简单补丁”功能的项目。但是这个“简单的补丁”使得小补丁的分发变得更容易一些,而复杂的补丁则成为版本控制的噩梦。由于错误的版本是令牌,我们经常不得不推送所有内容并不得不在客户站点跟踪问题。
我的结论是我使用了静态链接,但除外:
对于依赖于动态链接的插件之类的东西
当共享很重要时(多个进程同时使用的大型库,如 C/C++ 运行时、GUI 库……通常是独立管理的,并且 ABI 是严格定义的)
如果有人想使用“简单补丁”,我认为这些库必须像上面的大型库一样进行管理:它们必须几乎独立于定义的 ABI,不能被修复更改。
这很简单,真的。当您对源代码进行更改时,您是要等待 10 分钟以构建它还是等待 20 秒?二十秒是我能忍受的。除此之外,我要么拔出剑来,要么开始考虑如何使用单独的编译和链接将其带回舒适区。
动态链接的最佳示例是,当库依赖于使用的硬件时。在古代,C 数学库被决定为动态的,以便每个平台都可以使用所有处理器功能对其进行优化。
一个更好的例子可能是 OpenGL。OpenGL 是一种由 AMD 和 NVidia 以不同方式实现的 API。而且您无法在 AMD 卡上使用 NVidia 实现,因为硬件不同。因此,您不能将 OpenGL 静态链接到您的程序中。这里使用动态链接让 API 针对所有平台进行优化。
Static linking
是编译时的一个过程,当链接的内容被复制到主二进制文件中并成为单个二进制文件时。
缺点:
Dynamic linking
是加载链接内容时的运行时进程。该技术允许:
ABI
稳定性的主要文件[关于]缺点:
在类 Unix 系统上,动态链接会使“root”难以使用安装在偏僻位置的共享库的应用程序。这是因为动态链接器通常不会关注 LD_LIBRARY_PATH 或具有 root 权限的进程的等效项。有时,静态链接可以节省时间。
或者,安装过程必须找到库,但这会使多个版本的软件难以在机器上共存。
动态链接需要额外的时间让操作系统找到并加载动态库。使用静态链接,一切都在一起,它是一次性加载到内存中。
另请参阅DLL 地狱。在这种情况下,操作系统加载的 DLL 不是您的应用程序附带的,也不是您的应用程序期望的版本。
另一个尚未讨论的问题是修复库中的错误。
使用静态链接,您不仅需要重建库,还必须重新链接和重新分发可执行文件。如果该库仅在一个可执行文件中使用,这可能不是问题。但是需要重新链接和重新分配的可执行文件越多,痛苦就越大。
使用动态链接,您只需重建和重新分发动态库即可。
静态链接将程序所需的文件包含在单个可执行文件中。
动态链接是您通常认为的,它生成的可执行文件仍然需要 DLL 等位于同一目录中(或者 DLL 可能位于系统文件夹中)。
(DLL =动态链接库)
动态链接的可执行文件编译得更快,而且资源不那么重。
静态链接只为您提供一个 exe,为了进行更改,您需要重新编译整个程序。而在动态链接中,您只需要对 dll 进行更改,并且当您运行 exe 时,更改将在运行时获取。通过动态链接(例如:windows)更容易提供更新和错误修复。
有大量且不断增加的系统,其中极端水平的静态链接可以对应用程序和系统性能产生巨大的积极影响。
我指的是通常被称为“嵌入式系统”的东西,其中许多现在越来越多地使用通用操作系统,并且这些系统用于所有可以想象的事情。
一个极其常见的例子是使用 GNU/Linux 系统的设备,它们使用Busybox。我通过构建一个包含内核及其根文件系统的可引导 i386(32 位)系统映像将NetBSD发挥到了极致,后者包含一个crunchgen
带有硬链接的静态链接(by)二进制文件所有程序本身包含所有(最后计数为 274 个)标准全功能系统程序(除了工具链之外的大多数程序),并且它的大小小于 20兆字节(并且可能在只有 64MB 的系统中运行得非常舒适内存(即使根文件系统未压缩并且完全在 RAM 中),尽管我一直无法找到一个这么小的来测试它)。
在之前的文章中已经提到,静态链接的二进制文件的启动时间更快(而且它可以更快),但这只是图片的一部分,尤其是当所有目标代码都链接到同一个文件,尤其是当操作系统支持直接从可执行文件中按需分页代码时。在这种理想情况下,程序的启动时间实际上可以忽略不计,因为几乎所有的代码页都已经在内存中并且被 shell(以及init
任何其他可能正在运行的后台进程)使用,即使请求的程序没有自启动以来一直运行,因为可能只需要加载一页内存即可满足程序的运行时要求。
然而,这还不是全部。我通常还通过静态链接所有二进制文件来为我的完整开发系统构建和使用 NetBSD 操作系统安装。尽管这需要更多的磁盘空间(x86_64 总共约 6.6GB,包括工具链和 X11 静态链接)(特别是如果一个完整的调试符号表可用于所有程序另外约 2.5GB),结果仍然整体运行速度更快,对于某些任务,甚至比声称共享库代码页的典型动态链接系统使用更少的内存。磁盘便宜(甚至是快磁盘),缓存常用磁盘文件的内存也相对便宜,但CPU周期确实不便宜,并且为每个启动的每个进程支付启动ld.so
成本它启动的时间将花费数小时和数小时的 CPU 周期来完成需要启动许多进程的任务,尤其是当反复使用相同的程序时,例如开发系统上的编译器。静态链接的工具链程序可以将我的系统的整个操作系统多架构构建时间减少数小时。我还没有将工具链构建到我的单个crunchgen
'ed 二进制文件中,但我怀疑当我这样做时,由于 CPU 缓存的胜利,我会节省更多的构建时间。
另一个考虑因素是您在库中实际使用的目标文件(翻译单元)的数量与可用的总数。如果一个库是由许多目标文件构建的,但您只使用其中几个符号,这可能是支持静态链接的一个论据,因为您只链接您在静态链接时使用的对象(通常)并且不要t 通常带有未使用的符号。如果您使用共享库,则该库包含所有翻译单元,并且可能比您想要或需要的要大得多。