我有一种心理抽动,这让我不愿意在C和C ++等低级语言中使用大型库(如GLib或Boost)。在我看来,我认为:
嗯,这个图书馆有成千上万的 男人小时投入其中,而且一直如此 由了解更多的人创造 关于语言比以往任何时候都要多。 他们的作者和粉丝说 这些库快速可靠, 功能看起来真的 有用,它肯定会阻止我 从(严重)重新发明轮子。
但该死的,我永远不会用 该库中的每个功能。它的 太大了,它可能会变得臃肿 这些年来;这是另一个球 和链我的程序需要拖延。
Torvalds rant(虽然有争议)并不能让我放心。
我的想法有什么基础,还是我只是不合理和/或无知?即使我只使用大型库的一个或两个功能,通过链接到该库我会产生运行时性能开销吗?
我确信它还取决于具体的库是什么,但我通常很想知道大型库是否会在技术层面上固有地引入低效率。
当我没有技术知识知道我是否正确时,我已经厌倦了对此的痴迷,嘀咕和担心。
请把我从痛苦中解救出来!
答案 0 :(得分:30)
即使我只使用大型库的一个或两个功能,通过链接到该库我会产生运行时性能开销吗?
一般来说,没有。
如果所讨论的库没有很多与位置无关的代码,那么当动态链接器在请求时对库执行重定位时会有启动成本。通常,这是该计划启动的一部分。除此之外没有运行时性能影响。
链接器也擅长在构建时从静态链接库中删除“死代码”,因此您使用的任何静态库都将具有最小的开销。表演甚至没有进入。
坦率地说,你担心错误的事情。答案 1 :(得分:25)
我无法对GLib发表评论,但请记住,Boost中的很多代码都是仅限标题的,并且考虑到用户的C ++原则只支付他们正在使用的内容,这些库非常有效。有几个库要求您链接它们(正则表达式,文件系统浮现在脑海中),但它们是独立的库。使用Boost,您不会链接到大型整体库,而只能链接到您使用的较小组件。
当然,另一个问题是 - 替代方案是什么?您是否希望在需要时自行实现Boost中的功能?鉴于许多非常称职的人员已经研究过这些代码并确保它能够在众多编译器中运行并且仍然有效,这可能不是一项简单的任务。另外,你至少在一定程度上重新发明了轮子。恕我直言,你可以更有成效地度过这段时间。
答案 2 :(得分:13)
Boost不是一个大型图书馆。
它是许多小型图书馆的集合。它们中的大多数都很小,它们被包含在一两个标题中。使用boost::noncopyable
不会将boost::regex
或boost::thread
拖到您的代码中。他们是不同的图书馆。它们只是作为同一个库集合的一部分分发。但是你只需为你使用的那些付费。
但总的来说,因为大型图书馆确实存在,即使Boost不是其中之一:
我的想法有什么基础,还是我只是不合理和/或无知?即使我只使用大型库的一个或两个功能,通过链接到该库我会产生运行时性能开销吗?
没有基础,或多或少。 你可以自己测试一下。
编写一个小型C ++程序并进行编译。现在为它添加一个新函数,一个永远不会调用但定义的函数。再次编译程序。假设启用了优化,链接器会将其剥离,因为它未被使用。因此,包含其他未使用的代码的成本为零。
当然也有例外。如果代码实例化任何全局对象,那么可能不会删除这些对象(这就是包含iostream
标头增加可执行文件大小的原因),但一般情况下,您可以包含任意数量的标头并链接到任意数量的库,只要您不使用任何添加的代码,它就不会影响程序的大小,性能或内存使用情况。
另一个例外是,如果动态链接到.dll或.so,则必须分发整个库,因此不能删除未使用的代码。但是静态编译到您的可执行文件中的库(作为静态库(.lib或.a)或者只是作为包含的头文件)通常可以被链接器修剪掉,删除未使用的符号。
答案 3 :(得分:11)
大型库将从代码性能角度来看:
boost
的大多数部分不需要运行时二进制文件,则它们是“仅标头”)。虽然操作系统只会将库中实际使用的部分加载到RAM中,但它仍然可以加载超出您需要的数量,因为加载的粒度等于页面大小(但仅在我的系统上为4 Kb)。需要更多时间来加载动态链接器。每次加载程序时,动态链接器必须将您需要外部库的每个函数与其在内存中的实际地址相匹配。这需要一些时间,但只需要一点点(但是,它在加载许多程序时很重要,例如桌面环境的启动,但你没有选择)。
是的,每次调用共享(动态链接)库的外部函数时,它会在运行时进行一次额外的跳转和几次指针调整
从开发人员的性能角度来看:
添加外部依赖。您将取决于其他人。即使该库的免费软件,您需要额外的费用来修改它。一些veeery低级程序开发人员(我说的是操作系统内核)讨厌依赖任何人 - 这是他们的专业特权。咆哮。
但是,这可以被认为是一种好处。如果其他人习惯boost
,他们会在您的计划中找到熟悉的概念和术语,并且会更有效地理解和修改它。
较大的库通常包含特定于库的概念需要时间来理解。考虑Qt。它包含信号和插槽以及moc
相关的基础设施。与整个Qt的大小相比,学习它们只需要一小部分时间。但是如果你使用这么大的图书馆的一小部分,这可能是一个问题。
答案 4 :(得分:5)
过多的代码并没有神奇地使处理器运行得更慢。它只是坐在那里占据一点点记忆。
如果您是静态链接且链接器完全合理,那么它将仅包含您实际使用的函数。
答案 5 :(得分:4)
我喜欢的框架,库集和某些类型的开发工具这个术语是平台技术。平台技术的成本超出了对代码大小和性能的影响。
如果您的项目本身旨在用作库或框架,您最终可能会在使用您的库的开发人员上推动您的平台技术选择。
如果您以源代码形式分发项目,最终可能会对最终用户推送平台技术选择。
如果您没有静态链接所有选定的框架和库,最终可能会给最终用户带来库版本问题的负担。
编译时间会影响开发人员的工作效率。增量链接,预编译头,正确的头依赖关系管理等可以帮助管理编译时间,但不会消除与某些平台技术引入的大量内联代码相关的编译器性能问题。
对于作为源分发的项目,编译时会影响项目的最终用户。
许多平台技术都有自己的开发环境要求。这些要求可能会累积,使项目上的新开发人员能够复制允许编译和调试所需的环境变得困难和耗时。
使用一些平台技术实际上为项目创建了一种新的编程语言。这使新开发者更难以贡献。
所有项目都具有平台技术依赖性,但对于许多项目而言,将这些依赖性保持在最低限度是非常有益的。
答案 6 :(得分:3)
更大并不意味着更慢。与其他一些答案相反,完全存储在头文件中的库与存储在目标文件中的库之间没有固有的区别。
仅标题库可以具有间接优势。大多数基于模板的库必须只是标题(或者很多代码最终都在标题中),模板确实提供了很多优化机会。在典型的对象文件库中获取代码并将其全部移动到标题中不,但是,通常会产生很多好的效果(并且可能会导致代码膨胀)。
特定图书馆的真正答案通常取决于其整体结构。很容易将“Boost”视为巨大的东西。事实上,它是一个巨大的集合库,其中大多数都是单独的。关于Boost整体而言,你不能说得太多(有意义),因为个别库是由不同的人编写的,具有不同的技术,目标等。其中一些(例如Format,Assign)确实比几乎任何东西都慢你很可能自己做。其他人(例如游泳池)提供你自己可以做的事情,但可能不会,至少可以提高速度。一些人(例如uBlas)使用重型模板魔术比任何人都快,但我们中有一小部分人希望自己能够实现。
当然,有很多库确实是单独的大型库。在很多情况下,这些确实比你自己写的慢。特别是,他们中的许多人(大多数?)试图比你几乎可以自己写的任何东西更加通用。虽然这不会必然导致代码变慢,但在这个方向上肯定存在强趋势。与许多其他代码一样,当您在商业上开发库时,客户往往对功能更感兴趣而不是速度大小。
有些图书馆还会花费大量的空间,代码(通常至少有一点时间)来解决你可能根本不关心的问题。例如,几年前我使用了图像处理库。它对200多种图像格式的支持听起来真的令人印象深刻(并且在某种程度上确实如此)但我很确定我从未用它来处理超过十几种格式(我可能只支持一半的格式)许多)。 OTOH,尽管它仍然非常快。支持更少的市场可能会限制他们的市场到代码实际上会变慢的程度(例如,它比IJG更快地处理JPEG)。
答案 7 :(得分:3)
正如其他人所说,添加动态库时会有一些开销。首次加载库时,必须执行重定位,但如果正确编译库,这应该是次要成本。由于需要搜索的库的数量增加,因此查找单个符号的成本也增加了。
添加另一个动态库的内存成本在很大程度上取决于您实际使用的数量。在执行某些代码之前,不会从磁盘加载代码页。但是,将加载其他数据,如头文件,符号表和库文件中内置的哈希表,这些数据通常与库的大小成比例。
glibc的主要贡献者Ulrich Drepper有一个great document,它描述了动态库的过程和开销。
答案 8 :(得分:3)
如果动态链接这些库,则加载这些库时可能会有很小的开销。这通常只是程序运行时间的一小部分。
但是一旦加载完所有内容就没有开销。
如果您不想使用所有的提升,那么请不要。它是模块化的,因此您可以使用您想要的部件而忽略其余部分。
答案 9 :(得分: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
程序相同的代码大小。
总之,库大小决策对链接器的依赖性更高。具体而言,连接子的效率。如果有疑问,许多小型库将更少依赖链接器的效率,但会使构建过程更加复杂和缓慢。
答案 10 :(得分:2)
一般来说,与性能问题有关的事情并不是为了娱乐它们,因为这样做是为了猜测它们是一个问题,因为如果你不这样做知道他们是,你猜测,而猜测是“过早优化”背后的核心概念。与性能问题有关的是,当你拥有它们时,而不是之前,就诊断它们。问题几乎从来都不是你猜想的。 Here's an extended example.
如果你做了相当多的事情,你就会认识到导致性能问题的设计方法,无论是在代码中还是在库中。 (库肯定会出现性能问题。)当您了解并将其应用于项目时,那么在某种意义上就会过早地进行优化,但无论如何都会产生预期的效果,避免出现问题。如果我可以总结一下你可能会学到什么,那就是太多的抽象层,过度的类层次结构(特别是那些充满通知风格的更新)是性能问题的常见原因。
与此同时,我分享了对第三方图书馆等的谨慎态度。太多次我参与了一些项目,其中一些第三方软件包被“杠杆化”用于“协同作用”,然后供应商要么冒烟或放弃了产品,要么因为微软在操作系统中改变了东西而过时了。然后我们在第三方软件包上大量使用的产品开始不起作用,需要大量支出,而原始程序员早已不复存在。
答案 11 :(得分:1)
从技术上讲,答案是肯定的,他们确实如此。然而,这些低效率非常很少具有实际意义。我将在这里假设一个静态编译的语言,如C,C ++或D.
当可执行文件加载到现代操作系统的内存中时,地址空间只是映射到它。这意味着,无论exectable有多大,如果有未使用的整个页面大小的代码块,它们将永远不会触及物理内存。但是,你会浪费地址空间,偶尔这对32位系统来说可能有点重要。
当你链接到一个库时,一个好的链接器通常会抛弃你不使用的多余的东西,但是特别是在模板实例化的情况下,这并不总是会发生。因此,您的二进制文件可能比严格必要的大一点。
如果您的代码没有使用大量交错使用的代码,那么最终会浪费CPU缓存中的空间。但是,由于缓存行很小(通常为64字节),因此很少会发生实际的重要问题。
答案 12 :(得分:1)
“另一个球和链”。真的?
或者它是一个稳定,可靠的平台,使您的应用程序首先实现?
考虑到一些人可能喜欢“太大而且臃肿”的图书馆,因为他们将图书馆用于其他项目,并且非常信任它。
实际上,他们可能会特意拒绝使用您的软件,因为您避免使用明显的“太大而且臃肿”的库。
答案 13 :(得分:0)
问问自己你的目标是什么。它是今天的中端工作站 - 没问题。它是旧硬件,甚至是有限的嵌入式系统,它可能是。
正如之前的海报所说,只是在那里使用代码并不会降低性能(它可能会降低缓存的位置并增加加载时间)。
答案 14 :(得分:0)
fwiw,我在Microsoft Windows上工作,当我们构建Windows时;为 SIZE 编译的构建比为 SPEED 编译的构建更快,因为您减少了页面错误命中。
答案 15 :(得分:0)
FFTW和ATLAS是两个相当大的库。奇怪的是,他们在世界上最快的软件中扮演着重要角色,优化的应用程序可以在超级计算机上运行。不,使用大型库不会使代码变慢,尤其是当替代方案为您自己实现FFT或BLAS例程时。
答案 16 :(得分:-11)
你很担心,特别是在提升方面。这并不是因为任何人都认为他们不称职,而是由于两个问题。
对于通常有点实用的用户代码来说并不是那么糟糕,但在许多库中,所有内容都是根据其他模板或模板定义的,这些内容涉及多个项目(意味着指数模板代码爆炸)。
只需在iostream中添加大约3 MB(!!!)就可以了。现在添加一些提升废话,如果你明确声明一些特别奇怪的数据结构,你就有30 MB的代码。
更糟糕的是,你甚至无法轻易地描述这一点。我可以告诉你,我编写的代码和模板库中的代码之间的区别是DRAMATIC,但是对于更简单的方法,你可能会认为你从一个简单的测试中做得更糟,但代码膨胀的成本将在大型真实世界中占据一席之地。应用
但人们对此赞不绝口,并假装它与“设计”有关,所以人们会觉得这是你应该做的一切,而不仅仅是一些应该很少使用的极其专业的工具。如果有的话。