在某些情况下,是否有任何令人信服的性能原因选择静态链接而不是动态链接?我已经听过或阅读了以下内容,但是我对这个主题的了解不足,无法保证其准确性。
1)静态链接和动态链接之间的运行时性能差异通常可以忽略不计。
2)(1)如果使用使用概要文件数据来优化程序热路径的性能分析编译器,则不正确,因为使用静态链接,编译器可以同时优化您的代码和库代码。使用动态链接只能优化您的代码。如果大部分时间都花在运行库代码上,那将有很大的不同。否则,(1)仍然适用。
在某些情况下,是否有任何令人信服的性能原因选择静态链接而不是动态链接?我已经听过或阅读了以下内容,但是我对这个主题的了解不足,无法保证其准确性。
1)静态链接和动态链接之间的运行时性能差异通常可以忽略不计。
2)(1)如果使用使用概要文件数据来优化程序热路径的性能分析编译器,则不正确,因为使用静态链接,编译器可以同时优化您的代码和库代码。使用动态链接只能优化您的代码。如果大部分时间都花在运行库代码上,那将有很大的不同。否则,(1)仍然适用。
Answers:
进行一些编辑以在评论和其他答案中包含非常相关的建议。我想指出的是,您打破这种情况的方式在很大程度上取决于您计划在何种环境下运行。最小的嵌入式系统可能没有足够的资源来支持动态链接。稍大的小型系统可能会很好地支持动态链接,因为它们的内存足够小,可以使动态链接节省的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的15位跳转范围以及无条件的跳转和调用都不具有AFAIK。这就是为什么(来自发电机的)功能超过32K一直是一个问题,需要嵌入式蹦床。
但是在像Linux这样的流行x86操作系统上,您不必担心.so / DLL文件是否不是随gcc
开关生成的-fpic
(强制使用间接跳转表)。因为如果您不这样做,代码将像普通链接器将其重新定位一样被固定。但是,这样做会使代码段不可共享,并且需要将代码从磁盘到内存的完整映射,并在使用之前将其全部触摸(清空大多数缓存,击中TLB)。等等。当这被认为是缓慢的。
因此,您将不再有任何好处。
我不记得什么操作系统(Solaris或FreeBSD的)给我的问题,我的Unix编译系统,因为我只是不这样做的,不知道为什么它坠毁,直到应用我-fPIC
来gcc
。
动态链接是满足某些许可证要求(例如LGPL)的唯一实用方法。
我同意dnmckee提到的要点,此外:
进行静态链接生成的原因之一是要验证您完全关闭了可执行文件,即,正确解析了所有符号引用。
作为使用持续集成构建和测试的大型系统的一部分,夜间回归测试使用可执行文件的静态链接版本运行。有时,即使动态链接的可执行文件可以成功链接,我们也会看到符号无法解析并且静态链接将失败。
当共享库中深处的符号具有拼写错误的名称,因此不会静态链接时,通常会发生这种情况。无论使用深度优先评估还是宽度优先评估,动态链接程序都不会完全解析所有符号,因此您可以完成一个没有完全关闭的动态链接可执行文件。
1 /我去过的项目中,对动态链接与静态链接进行了基准测试,并且确定的差异还不足以切换到动态链接(我不是测试的一部分,我只知道结论)
2 /动态链接通常与PIC(位置独立代码,不需要根据其加载地址进行修改的代码)相关联。根据架构的不同,PIC可能会带来另一个速度下降,但是为了获得在两个可执行文件之间共享动态链接库的好处(如果操作系统使用加载地址的随机性作为安全措施,甚至是同一可执行文件的两个进程)共享,PIC是必需的。我不确定是否所有操作系统都可以将这两个概念分开,但是Solaris和Linux可以做到,HP-UX也可以做到ISTR。
3 /我参与过其他项目,这些项目使用动态链接实现“简易补丁”功能。但是,这种“简单补丁”使小补丁的分发更加容易,而复杂补丁的分发成为了噩梦。由于错误的版本是令牌,因此我们常常不得不进行所有工作,并且不得不在客户现场跟踪问题,最终导致失败。
我的结论是我使用了静态链接,但:
适用于依赖动态链接的插件之类的东西
共享很重要时(多个进程同时使用的大型库,例如C / C ++运行时,GUI库等),这些库通常是独立管理的,并且必须为其严格定义ABI)
如果要使用“简易补丁程序”,我认为必须像上面的大型库一样对库进行管理:它们必须几乎独立,并且已定义的ABI不能通过修复程序进行更改。
在类似Unix的系统上,动态链接可能会使“ root”难以使用在偏僻位置安装了共享库的应用程序。这是因为动态链接程序通常不会注意具有根特权的进程的LD_LIBRARY_PATH或其等效项。有时,静态链接可以节省一天的时间。
或者,安装过程必须找到这些库,但这会使软件的多个版本难以在计算机上共存。
LD_LIBRARY_PATH
并不是使用共享库的障碍,至少在GNU / Linux中没有。例如,如果将共享库放在../lib/
相对于程序文件的目录中,则使用GNU工具链,链接器选项-rpath $ORIGIN/../lib
将指定从该相对位置搜索库。然后,您可以轻松地将应用程序与所有关联的共享库一起重新定位。使用此技巧,就可以同时拥有多个版本的应用程序和库(假设它们是相关的,如果没有关系,则可以使用符号链接)。
/etc/ld.so.conf
针对这种情况进行编辑。
确实很简单。当您更改源代码时,是否要等待10分钟才能构建代码或20秒?我只能忍受20秒。除此之外,我要么剑拔or张,要么开始考虑如何使用单独的编译和链接将其带回舒适区域。
动态链接需要额外的时间,操作系统才能找到并加载动态库。使用静态链接,所有内容都在一起,并且一次性加载到内存中。
另请参阅DLL Hell。在这种情况下,操作系统加载的DLL不是您的应用程序随附的DLL,也不是您的应用程序期望的版本。
静态链接仅给您一个exe,以便进行更改,您需要重新编译整个程序。在动态链接中,您只需要对dll进行更改,而在运行exe时,这些更改将在运行时获取。通过动态链接(例如Windows)更容易提供更新和错误修复。
在数量众多且数量不断增加的系统中,极端级别的静态链接可能会对应用程序和系统性能产生巨大的积极影响。
我指的是通常被称为“嵌入式系统”的系统,其中许多现在越来越多地使用通用操作系统,并且这些系统用于所有可以想象的事情。
一个非常常见的示例是使用Busybox的使用GNU / Linux系统的设备。我通过构建可引导的i386(32位)系统映像(包括内核和其根文件系统)将NetBSD发挥到了极致,后者包含单个静态链接的(由crunchgen
)二进制文件以及与之的硬链接。所有本身包含所有标准功能系统程序的程序(最后计数为274)(除工具链以外的大多数程序),并且其大小小于20 兆字节(并且可能在只有64MB的系统中非常舒适地运行内存(即使根文件系统未压缩且完全在RAM中),尽管我无法找到这么小的内存来进行测试。
它已经在前面的帖子已经提到,静态链接二进制的启动时间更快(它可以是一个很大更快),但是这是图片中的一部分,尤其是当所有的对象代码被链接到同一个文件,尤其是在操作系统支持直接从可执行文件进行按需分页代码时。在这种理想情况下,程序的启动时间实际上可以忽略不计,因为几乎所有代码页都已经在内存中并且被外壳程序(以及init
可能正在运行的任何其他后台进程)使用,即使请求的程序没有自引导以来一直运行,因为可能只需要加载一页内存即可满足程序的运行时要求。
但是,这还不是全部。我通常还通过静态链接所有二进制文件来为我的完整开发系统构建和使用NetBSD操作系统安装。即使这会占用大量磁盘空间(x86_64总共约6.6GB,包括工具链和X11静态链接在内的所有内容)(尤其是如果一个程序对所有程序都保留了完整的调试符号表,而另外还有约2.5GB),结果仍然整体运行速度更快,并且某些任务甚至比声称共享库代码页的典型动态链接系统使用更少的内存。磁盘很便宜(甚至是快速磁盘),用于缓存常用磁盘文件的内存也相对便宜,但是CPU周期确实不是,并且ld.so
为每个启动的进程支付启动成本都要花启动所需的时间与需要启动许多进程的任务相比,将花费数小时的CPU周期,尤其是当一遍又一遍地使用相同的程序(例如开发系统上的编译器)时。静态链接的工具链程序可以将我的系统的全OS多体系结构构建时间减少数小时。我还没有将工具链构建到我的单个crunchgen
'ed二进制文件中,但是我怀疑这样做的时候,由于赢得了CPU缓存,因此可以节省更多的构建时间。