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