C与C++在内存分配性能方面的比较

12

我计划参与开发使用C语言编写的蒙特卡罗分析代码,该代码在内存中分配了大量数据数组以提高其性能。因此,代码的作者选择了C语言而不是C++,声称使用C语言可以编写更快速和更可靠(关于内存泄漏方面)的代码。

你同意这个观点吗?如果你需要在计算过程中存储4-16GB的数据数组,你会选择什么语言呢?


3
我认为分配16 GB 不会带来任何性能提升。空间也会影响时间。 - kennytm
16
可靠性的很大一部分来自经验。即使C++在资源管理方面比C更可靠(RAII在这方面帮助很多),有经验的C程序员可能会发现用C实现比用C++更容易(并且出错更少)。 可靠性来自程序员,而不是语言(语言可以帮助或妨碍,但主要部分是程序员)。 - David Rodríguez - dribeas
8个回答

24

毫无疑问是C++。默认情况下,这两者之间没有明显的区别,但是 C++提供了一些C不具备的能力:

  1. 构造函数/析构函数。它们允许您自动化大多数内存管理,提高可靠性。
  2. 按类分配器。这些允许您根据特定对象的设计和/或用法来优化分配。如果您需要大量小对象(一个明显的例子),这可能非常有用。

总之,在这方面,C绝对没有比C++更好的优势。在最坏的情况下,您可以以完全相同的方式做完全相同的事情。


1
@roe:从任何一种语言中获得最大的性能都需要小心谨慎——但构造函数和析构函数最终只是一种打包在 C 中执行的相同操作的方式。唯一的区别是它们使管理变得容易,以至于你经常会被诱惑去使用它们,即使在 C 中你甚至不会考虑这样做。 - Jerry Coffin
3
如果你在使用构造函数和析构函数,C++的速度肯定会变慢,因为它需要同时进行分配内存和初始化操作。 - paxdiablo
6
如果你需要初始化,无论是在构造函数中还是在其他地方的代码块中,都必须进行初始化。如果需要初始化内存,则必须分配并初始化。如果不需要初始化,则不需要用户声明构造函数,实现可以将其生成的构造函数优化为无操作。 - CB Bailey
2
@paxdiablo:根据所引用的性能和可靠性标准,这是我会选择的。C++在性能上并不一定有优势,但在最坏的情况下,它可以提供完全相同的性能,并且大约90%的时间它将至少具有轻微的优势。 - Jerry Coffin
3
即使只使用 C++ 的 C 子集,其可靠性比 C 更高。您可以添加几个 RAII 对象来帮助处理复杂函数中的内存泄漏问题(多个返回值、潜在错误快捷方式等),这样您的代码将更加可靠。 - David Rodríguez - dribeas
显示剩余4条评论

9
C99有一个特性,在C++中不存在,可以在重型数值计算代码中带来显著的速度提升,即关键字“restrict”。如果你使用支持该特性的C++编译器,则在优化时会多一种工具。不过,这只是潜在的收益:足够的内联可以实现与“restrict”相同的优化甚至更多。它也与内存分配无关。
如果代码作者能演示C和C++分配4-16GB数组的性能差异,则(a)我很惊讶,但好吧,确实有差异,(b)程序将分配这么大的数组多少次?你的程序实际上会花费大量时间分配内存,还是花费大部分时间访问内存并进行计算?与分配所需的时间相比,实际上执行4GB数组需要很长时间,这意味着你应该担心“任何事情”的性能,而不是分配的性能。短跑运动员非常关心起步的速度。马拉松选手则不太关心。
你还必须注意如何进行基准测试。例如,你应该将“malloc(size)”与“new char[size]”进行比较。如果你将“malloc(size)”与“new char[size]()”进行测试,则比较结果是不公平的,因为后者将内存设置为0,而前者不会。相反,应该与“calloc”进行比较,但也要注意,在C++中,“malloc”和“calloc”都是可用的(虽然可能性很小),如果它们确实证明更快,则可以使用它们。
不过,最终,如果作者“拥有”或开始了这个项目,并且更喜欢使用C而不是C++,那么他不应该通过可能虚假的性能声明来证明这个决定,而应该说“我更喜欢C,这就是我使用的语言”。通常,当有人提出关于语言性能的这种声明时,如果经过测试发现不是真的,你会发现性能并不是语言偏好的真正原因。证明这个声明是错误的,实际上并不能使这个项目的作者突然开始喜欢C++。

3
在内存分配方面,C和C++没有实质性的区别。如果你选择在对象上使用虚方法,C++会有更多“隐藏”的数据,例如虚指针等。但是,在C和C++中分配字符数组的代价是一样的,事实上它们可能都使用malloc来完成。在性能方面,C++为数组中的每个对象调用构造函数。请注意,只有在存在构造函数时才执行此操作,而默认构造函数不执行任何操作并被优化掉了。
只要您预先分配数据池以避免内存碎片,那么就可以放心使用。如果您有简单的POD结构体,没有虚方法和构造函数,则没有区别。

2
实际上,它们都使用malloc来完成这个操作。只是有点吹毛求疵,但并不一定正确;默认情况下,new/delete不必使用malloc和free。 - GManNickG
2
@GMan; 当然不是,应该加上“可能”的。我想G++默认会这样做。C也不必使用它。 :) - falstro
对于g++/gcc编译器,newmalloc最终都会调用brk函数。 - Dan Andreatta
@Dan Andreatta:仅限于Linux系统。 - Stephen Canon
@Stephen Canon:当然,没错。实际上只有在它们最终需要从操作系统获取新的内存池时才会这样做。 - Dan Andreatta

3
C++唯一不利的地方就是它的额外复杂性 - 如果程序员使用不当,很容易显著降低速度。如果不使用C++功能,使用C++编译器将获得相同的性能。正确使用C++,则有可能更快。
语言本身不是问题,分配和遍历大型数组才是问题。
在分配内存时,你可能犯的致命错误(无论使用哪种语言)是分配16G内存,将其初始化为零,然后再填充实际值。
我预计最大的性能提升来自于算法优化,这些优化可以改善引用的局部性。
根据底层操作系统的不同,您还可能会影响缓存算法 - 例如,表明内存范围仅以顺序方式处理。

2
为了分配原始数据,在大多数系统上,C和C++通常都使用相同的运行时库机制,因此它们之间不应该有任何区别。我想知道这是否是经典的基准测试陷阱,在其中他们还测量了C++中构造函数调用的运行时,并方便地忘记了在C中包括任何初始化代码的运行时。
此外,“更可靠(关于内存泄漏)”的论点如果你在C++中使用RAII(正如你应该做的那样)是站不住脚的。除非有人指的是使用RAII使其更容易发生泄漏,使用RAII、智能指针和容器类将减少潜在的泄漏可能性,而不是增加它。
我对分配那么多内存的主要关注点有两个:
- 如果你接近在Monte Carlo模拟机器上的物理内存限制,这是降低性能的好方法,因为当虚拟内存系统需要开始进行频繁的分页时,磁盘很可能开始进行切换。虚拟内存并不是“免费的”,尽管很多人认为它是。 - 必须仔细考虑数据布局以最大化处理器缓存的使用率,否则你将部分失去在第一次将数据保存在主内存中时的好处。

1
如果内存分配是代码瓶颈,我建议重新设计,而不是更改语言以获得更快的分配。如果你只分配一次内存,然后进行很多计算,我会预期这些计算成为瓶颈。如果分配成本显著,那么就有问题了。

0

在C++中,您也可以使用C系列的内存分配函数:标准的mallocfreerealloc用于扩大/缩小数组,以及alloca用于在堆栈上分配内存。

如果您选择使用new,它将分配比所需更多的内存(主要是在调试期间),并进行额外的一致性检查。它还将为类调用构造函数。在发布(-O3)版本中,对于大多数应用程序来说,差异将是微不足道的。

现在,new带来的与malloc不同之处在于就地new。您可以预先分配一个缓冲区,然后使用就地new将结构放入该缓冲区中,从而使其“分配”变得即时。

总的来说,我不会因为性能问题而远离C。如果有什么,您的代码将更有效率,因为类通过寄存器传递this指针,而不是像C等效果那样传递参数。远离C的真正原因是C++运行时的大小。如果您为嵌入式系统或引导加载程序开发程序,则无法嵌入约4MB的运行时。但是对于普通应用程序,这不会有任何影响。


0

如果您需要在计算过程中存储4-16 GB的数据数组,并且您的计算机只有2GB的物理内存,那么怎么办?

如果您的计算机有16GB的物理内存呢?操作系统是否不占用物理内存?

操作系统是否允许您使用4GB、16GB等地址空间?

我建议,如果性能是主要实现约束条件,那么了解所打算使用的平台如何运作和执行远比在相同环境和算法下C和C++之间的任何可衡量性能差异问题更为重要。


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