在大型应用中应避免使用STL吗?


24

这听起来像是一个很奇怪的问题,但是在我的部门中,我们遇到以下问题:

我们正在开发一个服务器应用程序,该应用程序正在变得越来越大,即使是在考虑将其拆分为不同部分(DLL文件)时,也可以在需要时动态加载,然后再卸载,以便能够处理性能问题。

但是:我们正在使用的函数将输入和输出参数作为STL对象传递,并且正如Stack Overflow答案中所述,这是一个非常糟糕的主意。(该帖子包含一些解决方案和技巧,但看起来并不十分牢固。)

显然,我们可以用标准C ++类型替换输入/输出参数,并从函数内部的那些对象创建STL对象,但这可能会导致性能下降。

是否可以得出结论,如果您正在考虑构建一个可能会增长到一台PC无法再处理的应用程序,您绝对不能将STL用作一项技术吗?

有关此问题的更多背景:该问题
似乎有一些误解:问题如下:
我的应用程序正在使用大量性能(CPU,内存)以完成其工作,我想分拆此工作分解为不同的部分(由于程序已经被拆分为多个功能),从我的应用程序中创建一些DLL并将某些功能放入这些DLL的导出表并不难。这将导致以下情况:

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1是安装在Machine1上的应用程序的实例,而App_Inst2是安装在Machine2上的同一应用程序的实例。
DLL1.x是安装在Machine1上的DLL,而DLL2.x是安装在Machine2上的DLL。
DLLx.1涵盖了导出的function1。
DLLx.2涵盖了导出的function2。

现在在Machine1上,我想执行function1和function2。我知道这会使Machine1重载,所以我想向App_Inst2发送一条消息,要求该应用程序实例执行function2。

function1和function2的输入/输出参数是STL(C ++标准类型库)对象,通常,我可能希望客户对App_Inst1,App_Inst2,DLLx.y进行更新(但不是全部,客户都可以升级Machine1,但是而不是Machine2,或者仅升级应用程序,而不升级DLL,反之亦然,...)。显然,如果接口(输入/输出参数)发生变化,则将迫使客户进行完全升级。

但是,如所引用的StackOverflow URL中所述,对App_Inst1或DLL中的一个进行简单的重新编译可能会导致整个系统崩溃,因此,我的原始帖子标题不建议使用STL(C ++标准模板)库)用于大型应用程序。

希望借此我清除一些问题/疑问。


44
您确定由于可执行文件大小而遇到性能问题吗?您是否可以添加一些详细信息,例如假设所有软件都是使用同一编译器编译的(例如在构建服务器上一次编译)是否现实,或者您是否真的想拆分成独立的团队?
nvoigt

5
基本上,您需要一个专职工作为“构建管理器”和“发布管理器”的人员,以确保所有C ++项目都在相同的编译器版本上进行编译,并且具有相同的C ++编译器设置,并从源的一致快照(版本)进行编译。代码等。通常在“持续集成”的旗帜下进行处理。如果您在线搜索,则会发现很多文章和工具。过时的做法可以自我强化-一种过时的做法可以导致所有过时的做法。
rwong

8
链接问题中可接受的答案指出,问题通常出在C ++调用上。因此,“ C ++而不是STL”无济于事,您需要使用裸C来保证安全(但请参阅答案,序列化可能是更好的解决方案)。
Frax

52
为了能够处理性能问题,在需要时进行动态加载,然后再进行卸载。 什么是“性能问题”?除了使用过多的内存(可以通过从内存中卸载DLL之类的东西来解决)之外,我什么都不知道。如果这是问题所在,最简单的解决方法就是购买更多的RAM。您是否已应用程序进行了概要分析,以确定实际的性能瓶颈?因为这听起来像XY问题,所以您尚未指定“性能问题”,并且已经有人决定了解决方案。
安德鲁·亨利

4
@MaxBarraclough“ STL”是已被C ++标准库包含的模板化容器和函数的替代名称,完全被接受。实际上,由Bjarne Stroustrup和Herb Sutter编写的C ++核心准则在谈论这些时反复提及“ STL”。您没有比这更权威的资料了。
肖恩·伯顿

Answers:


110

这是一个石冷的经典XY问题。

真正的问题是性能问题。但是,您的问题清楚地表明,您没有对性能问题的实际来源进行性能分析或其他评估。相反,您希望将代码拆分为DLL可以神奇地解决问题(就记录而言,它不会),现在您担心该非解决方案的一个方面。

相反,您需要解决实际的问题。如果您有多个可执行文件,请检查是哪一个导致速度下降。在使用它时,请确保它实际上是程序占用了所有处理时间,而不是配置错误的以太网驱动程序或类似的东西。然后,开始分析代码中的各种任务。高精度计时器是您的朋友。经典的解决方案是监视一段代码的平均和最坏情况下的处理时间。

掌握数据后,您可以确定如何解决问题,然后可以确定在哪里进行优化。


54
“相反,您希望将代码拆分为DLL将神奇地解决问题(就记录而言,它不会)” –为此+1。您的操作系统几乎可以肯定地实现了需求分页,该分页仅在自动执行而不需要手动干预的情况下实现与DLL中的加载和卸载功能完全相同的结果。即使您能更好地预测一段代码使用后应该比操作系统虚拟内存系统挂了多久(这实际上是不可能的),但操作系统仍会缓存DLL文件并反而会抵消您的努力
朱尔斯

@Jules请参阅更新-他们已经澄清了DLL仅存在于单独的计算机上,因此我也许可以看到此解决方案有效。但是现在存在通信开销,因此很难确定。
伊兹卡塔'18

2
@Izkata-尚不完全清楚,但我认为所描述的是他们希望动态选择(基于运行时配置)每个本地或远程功能的版本。但是,从未在给定计算机上使用过的EXE文件的任何部分都永远不会加载到内存中,因此不需要为此目的使用DLL。只需在标准版本中同时包含所有函数的两个版本,然后创建函数指针表(或C ++可调用对象,或您希望使用的任何方法),以调用每个函数的适当版本。
朱尔斯

38

如果必须在多个物理计算机之间拆分软件,则在计算机之间传递数据时必须具有某种形式的序列化,因为只有在某些情况下,您才可以在计算机之间实际发送相同的二进制文件。大多数序列化方法在处理STL类型时都没有问题,因此这种情况不会令我担心。

如果必须将应用程序拆分为共享库(DLL)(出于性能方面的考虑,在执行此操作之前,您确实应确保它实际上会解决您的性能问题),但传递STL对象可能是一个问题,但并非必须如此。正如您提供的链接已经描述的那样,如果您使用相同的编译器和相同的编译器设置,则传递STL对象将起作用。如果用户提供DLL,则您可能无法轻松依靠它。如果提供所有DLL并一起编译所有内容,那么您也许可以依靠它,并且跨DLL边界使用STL对象的可能性很大。您仍然需要当心编译器设置,以便在传递对象所有权时不会得到多个不同的堆,尽管这不是STL特定的问题。


1
是的,尤其是有关跨DLL / so边界传递分配的对象的部分。一般来说,绝对避免多重分配器问题的唯一方法是确保分配结构的DLL / so(或库!)也可以释放它。这就是为什么您看到很多用这种方式编写的C样式API的原因:每个API都有一个显式的免费API,传回了分配的数组/结构。STL的另一个问题是,调用者可能希望能够修改传递的复杂数据结构(添加/删除元素),并且也不允许这样做。但是很难执行。
davidbak

1
如果我必须拆分这样的应用程序,则可能会使用COM,但这通常会增加代码大小,因为每个组件都带有自己的C和C ++库(它们在相同时可以共享,但在必要时可以分开, 。例如,转换过程中,我不认为这是为OP的问题适当的行动过程中,虽然。
西蒙·里克特

2
作为一个具体的例子,该方案极有可能的地方要送一些文字到另一台机器。在某个时候,将有一个指针指向表示该文本的某些字符。您绝对不能只传输那些指针的位并在接收端期望已定义的行为
Caleth,

20

我们正在处理一个服务器应用程序,该应用程序正在变得越来越大,即使是在考虑将其拆分为不同部分(DLL)的时候,也可以在需要时动态加载,然后再卸载,以便能够处理性能问题

RAM很便宜,因此非活动代码也很便宜。加载和卸载代码(尤其是卸载)是一个脆弱的过程,不太可能对现代台式机/服务器硬件上的程序性能产生重大影响。

缓存更昂贵,但是只会影响最近处于活动状态的代码,而不会影响未使用的内存中的代码。

通常,程序由于数据大小或CPU时间而不是代码大小而超出了他们的计算机。如果您的代码大小太大而导致严重的问题,那么您很可能首先想看看为什么会发生这种情况。

但是:我们正在使用的函数将输入和输出参数作为STL对象传递,并且正如此StackOverflow URL中所述,这是一个非常糟糕的主意。

只要dll和可执行文件都使用相同的编译器构建并针对相同的C ++运行时库动态链接,就可以了。因此,如果将应用程序及其关联的dll作为单个单元构建和部署,那么这应该不是问题。

问题可能出在库是由不同的人构建还是可以分别进行更新的时候。

是否可以得出结论,如果您正在考虑构建一个可能增长到一台PC无法再处理的应用程序,您绝对不能将STL用作一项技术吗?

并不是的。

一旦开始将应用程序分布到多台计算机上,就需要考虑如何在这些计算机之间传递数据的全部注意事项。是否使用STL类型或更多基本类型的详细信息可能会在噪声中丢失。


2
不活动的代码很可能根本不会首先加载到RAM中。大多数操作系统仅在实际需要时才从可执行文件中加载页面。
朱尔斯

1
@Jules:如果将无效代码与实时代码混合在一起(页面大小= 4k粒度),则将其映射+加载。缓存以更精细的(64B)粒度工作,因此,大多数情况下,未使用的功能并不会带来太大的伤害。但是,每个页面都需要一个TLB条目,并且(不同于RAM)是稀缺的运行时资源。(文件支持的映射通常不使用大页面,至少在Linux上不使用;一个大页面是x86-64上的2MiB,因此您可以覆盖更多代码或数据,而不会因大页面而导致TLB遗漏。)
Peter Cordes

1
@PeterCordes指出:因此,请确保在发布发布过程中使用“ PGO”!
JDługosz

13

不,我认为不会得出结论。即使您的程序分布在多台计算机上,也没有理由在内部使用STL强制您在模块间/进程间通信中使用它。

实际上,我认为您应该从一开始就将外部接口的设计与内部实现分开,因为与内部使用的方法相比,前者将更牢固/更难更改


7

您错过了这个问题的重点。

基本上有两种类型的DLL。你自己的,别人的。“ STL问题”是您和他们可能未使用同一编译器。显然,这对于您自己的DLL而言不是问题。


5

如果您使用相同的编译器和构建选项同时在同一源树中构建DLL,则它将正常运行。

但是,将应用程序分为多个部分的“ Windows风格”方式是COM组件,其中一些可重复使用。它们可以很小(单个控件或编解码器),也可以很大(IE可以作为ms控件中的COM控件,在mshtml.dll中)。

在需要时动态加载,然后再卸载

对于服务器应用程序,这可能会带来可怕的效率。只有当您的应用程序在很长一段时间内经历多个阶段时,它才真正可行,这样您才知道何时不再需要某些东西。它使我想起使用叠加机制的DOS游戏。

此外,如果您的虚拟内存系统运行正常,它将通过调出未使用的代码页来为您处理。

可能会增长到一台PC无法再处理的程度

购买更大的PC。

不要忘记,通过正确的优化,笔记本电脑可以胜过hadoop集群。

如果您确实需要多个系统,则必须非常仔细地考虑它们之间的边界,因为这是序列化成本所在。在这里,您应该开始研究MPI之类的框架。


1
“只有当您的应用程序在很长一段时间内会经历多个阶段,这样您才知道何时不再需要某些东西时,它才真正可行” –即使那样,它也不会有太大帮助,因为操作系统会缓存DLL文件,这可能最终会占用更多的内存,而不仅仅是直接在基本可执行文件中包含功能。覆盖仅在没有虚拟内存的系统中或虚拟地址空间是限制因素时才有用(我假设此应用程序是64位而不是32位...)。
朱尔斯

3
“购买更大的PC” +1。现在,您可以获取具有多个TB的RAM的系统。您可以以不到单个开发人员每小时的费率从Amazon租用一个。您将花费多少开发人员时间来优化代码以减少内存使用量?
朱尔斯

2
我遇到的“购买更大的PC”最大的问题与“您的应用程序可扩展到多大?”这个问题有关。我的回答是“您愿意花多少钱进行测试?因为我希望测试规模如此之大,以至于租用一台合适的机器并设置一个合适的大型测试将花费数千美元。我们的客户中没有一个能与之比得上单CPU PC可以做什么。”。许多较老的程序员并不现实,不知道多少PC已经长大了。按照20世纪的标准,仅现代PC中的视频卡就是一台超级计算机。
MSalters

COM组件?也许在1990年代,但现在呢?
彼得·莫滕森

@MSalters-对...任何对应用程序可在单个PC上扩展的范围有任何疑问的人,都应查看Amazon EC2 x1e.32xlarge实例类型的规格-该计算机中总共有72个物理处理器内核,可在以下位置提供128个虚拟内核2.3GHz(可扩展至3.1GHz),潜在的高达340GB / s的内存带宽(取决于所安装的内存类型,在规范中未描述)和3.9TiB的RAM。它有足够的缓存来运行大多数应用程序,而无需接触主RAM。即使没有GPU,它的功能也与2000
以来

0

我们正在开发一个服务器应用程序,该应用程序正在变得越来越大,即使是在考虑将其拆分为不同部分(DLL文件)时,也可以在需要时动态加载,然后再卸载,以便能够处理性能问题。

第一部分很有意义(出于性能原因,将应用程序拆分到不同的机器上)。

第二部分(加载和卸载库)没有意义,因为这是额外的工作,并且不会(确实)改进。

使用专用的计算机可以更好地解决您所描述的问题,但这些计算机不能与同一(主)应用程序一起使用。

经典解决方案如下所示:

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

在前端计算机和计算机之间,您可能需要执行其他工作,例如负载平衡器和性能监视,并且在专用计算机上保留专门的处理对于缓存和吞吐量优化非常有用。

这绝不意味着额外地加载/卸载DLL,也与STL无关。

也就是说,根据需要在内部使用STL,并在元素之间序列化数据(请参阅grpc和协议缓冲区以及它们解决的问题的类型)。

这就是说,在您提供的信息有限的情况下,这确实像经典的xy问题(如@Graham所说)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.