包含一个库会带来运行时性能成本吗?

4

在包含整个库(可能有数百个函数)并仅使用单个函数(如以下示例)之间,是否存在运行时性能差异

#include<foo>

int main(int argc, char *argv[]) {
    bar();//from library foo
    return 0;
}

在将库中的相关代码片段直接粘贴到代码中之间,例如:

void bar() {
...
}

int main(int argc, char *argv[]) {
    bar();//defined just above
    return 0;
}

什么会阻止我在我的C文件开头毫无思考地包含所有我最喜欢(和最常用)的库?这个流行的主题 C/C++:检测多余的#include?表明编译时间会增加。但是编译后的二进制文件是否有任何不同?第二个程序是否实际上执行效果更好?
相关: 在C程序中,#includelt;stdio.h>实际上是做什么的

编辑:这里的问题与相关的问题在C / C ++中包括未使用的头文件是否会影响性能?不同,因为这里只包括一个文件。我在此询问是否包含单个文件与将实际使用的代码片段复制粘贴到源中有任何不同。我稍微调整了标题以反映这种差异。


2
我的经验法则是只包含必要的内容。如果您包含不必要的内容,可能会使人们感到困惑,因为他们可能认为您纳入这些内容是有原因的,而实际上并非如此。 - NathanOliver
6
您展示的内容包括头文件。头文件主要由声明组成,这些声明并不会对性能产生影响。您可能唯一破坏的是编译时间。但是说到库——一个不错的链接器会自动消除所有未使用的东西。 - Eugene Sh.
@Eugene,你应该把这个变成一个答案!;) - mame98
您可能需要注意,您的评论适用于静态链接库(而不是大多数现代系统的默认选项)。动态链接库有些不同。 - Martin York
显示剩余8条评论
4个回答

13

就最终程序而言,库中未使用的函数不会被链接,因此不会导致性能差异。

如果包含了很多库,编译程序可能需要更长时间。

你不应该包含所有你“喜爱的库”的主要原因是为了程序设计。你的文件不应包含除了它正在使用的资源之外的任何东西,以减少文件之间的依赖关系。你的文件对程序的其余部分知道得越少,越好,它应该尽可能自主。


假设你正在编写一个链表ADT。常识告诉我们,每个使用你的链表的人都不应该被强制包含数学库、SQL数据库、3D图形等等。在程序设计时应用常识将使你走得更远。 - Lundin
如果库接口没有使用所提到的库中定义的任何类型,那么用户就没有理由“被迫”包含它们,对吧?但是,如果它确实在使用,并且相应的头文件没有包含它们,那么从任何意义上来说,这都不是一个正确编写的头文件。 - Eugene Sh.
如果是这种情况,那么库就会出问题。 - Lundin
1
连接器只会将实际使用的函数链接到您的程序中。这当然取决于连接器以及库的构建方式。如果库源代码只有一个 .c 文件,其中包含许多函数,那么使用其中的一个函数可能会获取整个包。 - chux - Reinstate Monica

8
这并不是一个简单的问题,所以也不值得给出简单的答案。当您确定什么更具执行效率时,有很多需要考虑的方面。
1.编译器和链接器: 不同的编译器会以不同的方式进行优化。这是很容易被忽视的事情,可能会在做一般性规划时引起一些问题。大部分现代编译器和链接器会将二进制文件优化为只包括执行所需的100%内容。但并不是所有编译器都会优化您的二进制文件。
2.动态链接: 在使用其他库时,有两种链接类型。它们的行为方式相似,但本质上是不同的。当您与动态库链接时,该库将保持与程序分离,并仅在运行时执行。动态库通常称为共享库,因此应将其视为由多个二进制文件使用。由于这些库通常是共享的,链接器不会从库中删除任何功能,因为链接器不知道该系统或操作系统中所有二进制文件所需的那些库的哪些部分。因此,与动态库连接的二进制文件的性能会受到轻微影响,特别是在启动程序后立即执行。随着动态联接数量的增加,这种性能损耗也会增加。
3.静态链接: 当您使用一个优化链接器将二进制文件与静态库链接时,链接器会“知道”您需要从该特定库中使用哪些功能,并删除不会在结果二进制文件中使用的功能。因此,二进制文件将变得更加高效,因此更具执行效率。但是这样做也有代价。
例如,假设您有一个操作系统,该操作系统在整个系统或操作系统中的大量二进制文件中广泛使用某个库。如果您将该库构建为共享库,则所有二进制文件都会共享该库,而可能使用不同的功能。现在假设您静态地连接每个二进制文件到该库。你最终会得到一个包含二进制功能的大量重复的内容,因为每个二进制文件都会复制它所需的功能。
总之,在问什么会使我的程序更具执行效率之前,请注意在您的情况下什么更具执行效率。如果您的程序打算占用大部分CPU时间,则请使用静态链接库。如果您的程序只会偶尔运行,则使用动态链接库以减少磁盘使用量。还值得注意的是,使用基于头文件的库仅会比静态链接二进制文件稍微提高一点性能(如果有的话),并且会大大增加编译时间。

你忽略了在非Windows平台上调用共享库中函数的每次调用成本。Unix系统通过强制所有对共享库中函数的调用都通过PLT进行惰性动态链接。一旦动态链接完成,x86架构只需要一个额外的jmp rel32 - Peter Cordes
@PeterCordes 那个链接已经失效了。在wayback机器上有一个版本保存在这里 - MT0
@MT0:谢谢。并更新我的先前评论:现在它使用jmp [disp32](32位)或jmp [RIP+rel32](64位)通过GOT条目进行间接跳转,而不是重写直接跳转中的rel32。这避免了需要对PLT或任何页面进行写入+执行权限。但是,gcc -fno-plt通过GOT内联间接调用,因此我认为它现在与Windows在以这种方式构建的程序中所做的相同。(这强制进行早期绑定,而不是懒惰。) - Peter Cordes

2
这取决于库的结构以及编译器实现方式。链接器(ld)只会汇总代码中引用的库,所以如果你有一个库中的两个函数a和b,但是只有对a的引用,那么函数b可能根本不会出现在最终的代码中。
头文件(包括)如果只包含声明,并且这些声明不会导致对库的引用,那么你应该看不到什么区别,无论是只输入你需要的部分(按照你的示例)还是包括整个头文件。
历史上,链接器ld会按文件提取代码,因此只要每个函数a和b在创建库时位于不同的文件中,就不会有任何影响。
然而,如果库没有精心构建,或者编译器实现方式确实从库中拉取了所有代码,无论是否需要,那么你可能会遇到性能问题,因为你的代码会更大,可能更难适应CPU缓存,CPU执行管道偶尔需要等待从主内存而不是缓存中获取指令。

0

这很大程度上取决于所涉及的库。

它们可能会初始化全局状态,从而减慢程序的启动和/或关闭速度。或者它们可能会启动执行与您的代码并行的线程。如果您有多个线程,这也可能会影响性能。

一些库甚至可能修改现有的库函数。也许是为了收集有关内存或线程使用情况的统计信息,或者出于安全审计目的。


库能否做任何主程序没有告诉它要做的事情? - Eugene Sh.
@EugeneSh。你的主程序中的函数调用可能会导致库中的内部函数调用。如果你的程序没有调用库中的任何函数,那么库中的代码将不会被链接或执行。 - Lundin
@Lundin 当然。但在这种情况下,你无法判断它们是否未使用。你仍然需要将它们链接起来。 - Eugene Sh.
1
一个C++库可以有全局对象,这些对象具有非平凡的构造函数。仅仅链接库可能会导致此代码运行。这取决于工具链和链接器选项。虽然通常需要代码显式地使用库中至少一个符号,但确实需要这样做。 - Marvin Sielenkemper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接