48

我有一种心理抽搐,这让我不愿意在 C 和 C++ 等低级语言中使用大型库(如GLibBoost )。在我看来,我认为:

嗯,这个库投入了数千个工时,它是由比我更了解该语言的人创建的。他们的作者和粉丝说这些库快速可靠,功能看起来非常有用,它肯定会阻止我(严重)重新发明轮子。

但该死的,我永远不会使用那个库中的每个函数。它太大了,多年来可能已经变得臃肿;这是我的程序需要拖动的另一个球和链条。

Torvalds的咆哮(尽管有争议)也没有让我的心放松。

我的想法有什么根据,还是我只是不合理和/或无知?即使我只使用大型库的一两个功能,通过链接到该库是否会产生运行时性能开销?

我确信这也取决于特定的库是什么,但我通常想知道大型库是否会在技术层面上固有地引入低效率。

当我没有技术知识来判断我是否正确时,我已经厌倦了痴迷、喃喃自语和担心这一点。

请让我摆脱痛苦!

4

17 回答 17

30

即使我只使用大型库的一两个功能,通过链接到该库是否会产生运行时性能开销?

一般来说,没有。

如果所讨论的库没有很多与位置无关的代码,那么当动态链接器在请求时对库执行重定位时,就会产生启动成本。通常,这是程序启动的一部分。除此之外没有运行时性能影响。

链接器还擅长在构建时从静态链接库中删除“死代码”,因此您使用的任何静态库都将具有最小的大小开销。性能甚至没有进入它。

坦率地说,你担心的是错误的事情。

于 2010-02-11T20:29:33.293 回答
25

我无法对 GLib 发表评论,但请记住,Boost 中的许多代码都是仅标头代码,并且考虑到用户只需为所使用的内容付费的 C++ 原则,这些库非常高效。有几个库需要您链接它们(正则表达式,文件系统浮现在脑海中),但它们是独立的库。使用 Boost,您不会链接到大型单体库,而只会链接到您使用的较小组件。

当然,另一个问题是——有什么替代方案?您想在需要时自己实现 Boost 中的功能吗?鉴于许多非常有能力的人已经处理过这段代码,并确保它可以跨多种编译器工作并且仍然高效,这可能不是一项简单的工作。另外,至少在一定程度上,您正在重新发明轮子。恕我直言,您可以更高效地度过这段时间。

于 2010-02-11T20:30:43.887 回答
13

Boost 不是一个大库。

它是许多小型图书馆的集合。它们中的大多数都非常小,它们包含在一两个标题中。使用boost::noncopyable不会拖入boost::regexboost::thread拖入您的代码。它们是不同的库。它们只是作为同一个图书馆收藏的一部分分发。但是您只需为您使用的那些付费。

但总的来说,因为确实存在大型库,即使 Boost 不是其中之一:

我的想法有什么根据,还是我只是不合理和/或无知?即使我只使用大型库的一两个功能,通过链接到该库是否会产生运行时性能开销?

没有根据,或多或少。你可以自己测试一下。

编写一个小的 C++ 程序并编译它。现在向它添加一个新函数,一个从未调用但已定义的函数。再次编译程序。假设启用了优化,它会被链接器剥离,因为它没有被使用。因此,包含额外未使用代码的成本为零。

当然,也有例外。如果代码实例化了任何全局对象,这些对象可能不会被删除(这就是为什么包含iostream头文件会增加可执行文件大小的原因),但通常,您可以包含任意数量的头文件并链接到任意数量的库,但它不会影响程序的大小、性能或内存使用 *只要您不使用任何添加的代码。

另一个例外是,如果您动态链接到 .dll 或 .so,则必须分发整个库,因此不能删除未使用的代码。但是静态编译到可执行文件中的库(作为静态库(.lib 或 .a)或作为包含的头文件)通常可以由链接器修剪,删除未使用的符号。

于 2010-02-11T22:29:37.820 回答
11

从代码性能的角度来看,大型库将:

  • 占用更多内存,如果它有一个运行时二进制文件(大部分boost不需要运行时二进制文件,它们是“仅标题”)。虽然操作系统只会将库中实际使用的部分加载到 RAM 中,但它仍然可以加载超过您需要的内容,因为加载内容的粒度等于页面大小(不过,在我的系统上只有 4 Kb)。
  • 如果需要运行时二进制文件,则需要更多时间通过动态链接器加载。每次加载程序时,动态链接器都必须将您需要外部库包含的每个函数与其在内存中的实际地址相匹配。这需要一些时间,但只是一点点(但是,它在加载许多程序的规模上很重要,例如桌面环境的启动,但你没有选择)。

    是的,每次调用共享(动态链接)库的外部函数时,都会在运行时进行一次额外的跳转和几次指针调整

开发人员的性能角度来看:

  • 添加外部依赖项。你会依赖别人。即使该库的免费软件,您也需要额外的费用来修改它。一些非常低级程序的开发人员(我指的是操作系统内核)讨厌依赖任何人——这是他们的专业技能。因此,咆哮。

    但是,这可以被认为是一种好处。如果其他人习惯了boost,他们会在你的程序中找到熟悉的概念和术语,并且会更有效地理解和修改它。

  • 较大的库通常包含特定于库的概念,这些概念需要时间来理解。考虑 Qt。它包含信号和槽以及moc相关的基础设施。与整个 Qt 的大小相比,学习它们只需要一小部分时间。但是,如果您使用这么大图书馆的一小部分,那可能是个问题。

于 2010-02-11T20:42:42.640 回答
5

多余的代码不会神奇地使处理器运行速度变慢。它所做的只是坐在那里占用一点内存。

如果您正在静态链接并且您的链接器完全合理,那么它只会包含您实际使用的功能。

于 2010-02-11T20:27:34.413 回答
4

我喜欢框架、库集和某些类型的开发工具的术语是平台技术。平台技术的成本超出了对代码大小和性能的影响。

  1. 如果您的项目本身打算用作库或框架,那么您最终可能会将您的平台技术选择推给使用您的库的开发人员。

  2. 如果您以源代码形式分发项目,您最终可能会将平台技术选择推给最终用户。

  3. 如果您没有静态链接您选择的所有框架和库,您最终可能会给最终用户带来库版本控制问题的负担。

  4. 编译时间会影响开发人员的工作效率。增量链接、预编译头、适当的头依赖管理等可以帮助管理编译时间,但不能消除与某些平台技术引入的大量内联代码相关的编译器性能问题。

  5. 对于作为源代码分发的项目,编译时间会影响项目的最终用户。

  6. 许多平台技术都有自己的开发环境要求。这些要求会累积起来,使得项目中的新开发人员能够复制允许编译和调试所需的环境变得困难且耗时。

  7. 使用某些平台技术实际上为项目创建了一种新的编程语言。这使得新开发人员更难做出贡献。

所有项目都具有平台技术依赖性,但对于许多项目而言,将这些依赖性保持在最低限度确实有好处。

于 2010-02-12T02:26:59.513 回答
3

如果这些库是动态链接的,则加载这些库时可能会有一点开销。这通常只占程序运行时间的一小部分。

但是,一旦加载了所有内容,就不会有任何开销。

如果你不想使用所有的提升,那就不要。它是模块化的,因此您可以使用您想要的部分而忽略其余部分。

于 2010-02-11T20:26:56.737 回答
3

更大并不意味着更慢。与其他一些答案相反,完全存储在标头中的库和存储在目标文件中的库之间也没有固有的区别。

仅标头库可以具有间接优势。大多数基于模板的库必须是仅头文件(或者很多代码最终都在头文件中),并且模板确实提供了很多优化机会。然而,在典型的目标文件库中获取代码并将其全部移动到标题中通常不会产生很多好的效果(并且可能导致代码膨胀)。

特定库的真正答案通常取决于其整体结构。很容易将“Boost”视为巨大的东西。事实上,它是一个庞大的库集合,其中大部分都非常小。您不能对整个 Boost 说太多(有意义的),因为各个库是由不同的人编写的,具有不同的技术、目标等。其中一些(例如 Format、Assign)确实比几乎任何东西都慢你很可能会自己做。其他人(例如 Pool)提供了您可以自己做但可能不会做的事情,以至少获得较小的速度改进。少数人(例如 uBlas)使用重型模板魔法来运行比任何人都快,但我们中的一小部分人希望自己实现。

当然,有相当多的库确实是独立的大型库。在很多情况下,这些确实比您自己编写的要慢。特别是,它们中的许多(大多数?)试图比您自己编写的几乎任何东西都更通用。虽然这并不一定会导致代码变慢,但在这个方向上肯定有很强的趋势。与许多其他代码一样,当您在商业上开发库时,客户往往对功能更感兴趣,而不是速度大小之类的东西。

一些库还投入大量空间、代码(通常至少是一点时间)来解决您可能根本不关心的问题。例如,几年前我使用了一个图像处理库。它对 200 多种图像格式的支持听起来确实令人印象深刻(并且在某种程度上确实如此),但我很确定我从未使用它来处理超过十几种格式(而且我可能只支持其中的一半)许多)。OTOH,尽管如此,它仍然非常快。支持更少的市场可能会限制他们的市场,以至于代码实际上会更慢(例如,它处理 JPEG 比 IJG 更快)。

于 2010-02-11T21:29:52.980 回答
3

正如其他人所说,添加动态库时会有一些开销。首次加载库时,必须执行重定位,尽管如果库被正确编译,这应该是一个很小的成本。由于需要搜索的库数量增加,查找单个符号的成本也增加了。

添加另一个动态库的内存成本很大程度上取决于您实际使用了多少。一页代码在执行之前不会从磁盘加载。但是,会加载其他数据,例如内置在库文件中的标头、符号表和哈希表,这些数据通常与库的大小成正比。

glibc 的主要贡献者 Ulrich Drepper有一个很棒的文档,描述了动态库的过程和开销。

于 2010-02-11T23:13:45.657 回答
2

取决于链接器的工作方式。一些链接器是惰性的,会包含库中的所有代码。更高效的链接器只会从库中提取所需的代码。我对这两种类型都有经验。

对于任何一种类型的链接器,较小的库都不会担心。小型库的最坏情况是少量未使用的代码。许多小型库可能会增加构建时间。权衡将是构建时间与代码空间。

链接器的一个有趣测试是经典的Hello World程序:

#include <stdio>
#include <stdlib>
int main(void)
{
  printf("Hello World\n");
  return EXIT_SUCCESS;
}

由于它可能需要的所有格式,该printf函数有很多依赖关系。一个懒惰但快速的链接器可能包含一个“标准库”来解析所有符号。更高效的库将仅包含及其依赖项。这会使链接器变慢。printf

可以使用以下程序将上述程序与此程序进行比较puts

#include <stdio>
#include <stdlib>
int main(void)
{
  puts("Hello World\n");
  return EXIT_SUCCESS;
}

一般来说,puts版本应该比printf版本小,因为puts没有格式化需求,因此依赖较少。惰性链接器将生成与程序相同的代码大小printf

总之,库大小决定对链接器有更多的依赖性。具体来说,链接器的效率。如有疑问,许多小型库将较少依赖链接器的效率,但会使构建过程更加复杂和缓慢。

于 2010-02-11T23:25:45.940 回答
2
  1. 一般来说,与性能问题有关的事情不是取悦他们,因为这样做是在猜测他们是一个问题,因为如果你不知道他们是一个问题,那么你就是在猜测,而猜测是核心“过早优化”背后的概念。与性能问题有关的事情是,当你遇到它们时,而不是之前,诊断它们。这些问题几乎从来都不是你能猜到的。这是一个扩展的例子。

  2. 如果你这样做相当多,你就会认识到往往会导致性能问题的设计方法,无论是在你的代码中还是在库中。(库肯定会出现性能问题。)当您了解这一点并将其应用于项目时,从某种意义上说,您过早地进行了优化,但无论如何它具有避免问题的预期效果。如果我可以总结一下你可能会学到的东西,太多的抽象层和过分夸大的类层次结构(尤其是那些充满通知式更新的)往往是导致性能问题的原因。

同时,我分享您对 3rd-party 库等的谨慎。太多次我参与过一些项目,其中一些 3rd 方包被“利用”以实现“协同作用”,然后供应商要么烟消云散,要么放弃产品,要么因为微软改变了操作系统而让它过时。然后,我们严重依赖 3rd-party 包的产品开始无法正常工作,需要我们投入大量资金,而原来的程序员早已不在。

于 2010-02-12T01:52:43.550 回答
1

“另一个球和链子”。真的吗?

或者它是一个稳定、可靠的平台,可以让您的应用程序在首位?

考虑到有些人可能喜欢“太大而且……臃肿”的库,因为他们将它用于其他项目并且非常信任它。

事实上,他们可能拒绝弄乱你的软件,特别是因为你避免使用明显的“太大而且……臃肿”的库。

于 2010-02-11T20:32:03.993 回答
1

从技术上讲,答案是肯定的。然而,这些低效率在实际中很少是重要的。我将在这里假设一种静态编译的语言,如 C、C++ 或 D。

当一个可执行文件被加载到现代操作系统的内存中时,地址空间被简单地映射到它。这意味着,无论可执行文件有多大,如果有整个页面大小的代码块未被使用,它们将永远不会触及物理内存。但是,您会浪费地址空间,而且有时这在 32 位系统上可能会有点影响。

当您链接到一个库时,一个好的链接器通常会丢弃您不使用的多余内容,尽管尤其是在模板实例化的情况下,这并不总是会发生。因此,您的二进制文件可能比严格必要的要大一点。

如果你有一些你不使用的代码与你确实使用的代码交错使用,你最终可能会浪费 CPU 缓存中的空间。但是,由于高速缓存行很小(通常为 64 字节),因此很少会在实际重要的程度上发生这种情况。

于 2010-02-11T20:32:41.573 回答
0

问问自己你的目标是什么。它是今天的中端工作站吗?没问题。是较旧的硬件还是有限的嵌入式系统,那么它可能是。

正如之前的海报所说,仅仅拥有代码并不会在性能上花费太多(它可能会减少缓存的局部性并增加加载时间)。

于 2010-02-11T20:59:33.270 回答
0

fwiw,我在 Microsoft Windows 上工作,当我们构建 Windows 时;为SIZE编译的构建比为SPEED编译的构建更快,因为您需要更少的页面错误命中。

于 2010-02-11T22:51:58.250 回答
0

FFTW and ATLAS are two quite large libraries. Oddly enough, they play large roles in the fastest software in the world, applications optimized to run on supercomputers. No, using large libraries doesn't make your code slow, especially when the alternative is implementing FFT or BLAS routines for yourself.

于 2010-02-12T03:38:46.263 回答
-11

您担心是非常正确的,尤其是在提升方面。这并不是因为写它们的人不称职,而是由于两个问题。

  1. 模板只是天生臃肿的代码。这在 10 年前并不重要,但现在 CPU 比内存访问快得多,而且这种趋势仍在继续。我几乎可以说模板是一个过时的功能。

对于通常有点实用的用户代码来说并不是那么糟糕,但在许多库中,所有内容都是根据其他模板或多个项目上的模板定义的(意味着指数模板代码爆炸)。

只需添加 iostream 即可为您的代码增加大约 3 mb (!!!)。现在添加一些 boost 废话,如果你简单地声明几个特别奇怪的数据结构,你就有 30 mb 的代码。

更糟糕的是,您甚至无法轻松地对此进行分析。我可以告诉你我编写的代码和模板库中的代码之间的区别是巨大的,但是对于更天真的方法,你可能认为你从简单的测试中做得更糟,但代码膨胀的成本会在一个大的现实世界中使用它的工具应用程序。

  1. 复杂。当您查看 Boost 中的内容时,它们都会在很大程度上使您的代码复杂化。诸如智能指针、仿函数之类的东西,各种复杂的东西。现在,我不会说使用这些东西从来都不是一个好主意,但几乎所有这些都需要某种高昂的成本。特别是如果您不完全理解,我的意思是确切地说,它在做什么。

但是人们对它赞不绝口,并假装它与“设计”有关,所以人们会觉得这是你应该做所有事情的方式,而不仅仅是一些应该很少使用的极其专业的工具。如果曾经。

于 2010-02-11T21:30:18.320 回答