用C语言编写性能?[关闭]


32

我知道我经常听到C通常比C ++具有性能优势。在我意识到MSVC似乎甚至不支持C的最新标准之前,我并没有真正想到它,但据我所知,最新的版本支持C99。

我打算编写一个包含一些代码的库以在OpenGL中呈现,以便我可以重用它。我打算用C语言编写该库,因为在图形方面,任何性能的提高都是值得欢迎的。

但这真的值得吗?使用该库的代码很可能是用C ++编写的,而我通常更喜欢用C ++编写代码。

但是,如果产生的性能差异很小,我可能会选择C。

可能还需要注意的是,这个库将是我可以在Windows / OS X / Linux上使用的库,并且我很可能会原生编译所有内容(Windows的MSVC,OS X的Clang或GCC以及Linux的GCC。 。或可能是英特尔的所有编译器)。

我环顾四周,并找到了一些基准测试等,但是我所看到的所有内容都是针对GCC而不是MSVC和Clang的。此外,基准测试未提及所用语言的标准。有人对此有任何想法吗?

编辑:经过几年的经验,我只是想分享我对这个问题的观点。我最终用C ++编写了一个问这个问题的项目。我大约在同一时间在C中启动了另一个项目,因为我们希望获得尽可能少的性能,并且需要该项目在C中可链接。几个月前,我到达了真正需要地图和高级界面的地步字符串操作。我知道C ++标准库中的这种功能,并最终得出结论:标准库中的那些结构可能会比在合理的时间内可以在C中实现的映射和字符串更好并且更稳定。通过向C ++代码编写C接口,可以很容易地满足在C中可链接的要求,该接口可以通过不透明类型快速完成。用C ++重写库似乎比用C编写库快得多,而且不容易出现错误,尤其是内存泄漏。我还能够使用标准库线程库,该库比使用特定于平台的实现要容易得多。最后,我相信用C ++编写库可以带来巨大的好处,而性能成本可能很小。我尚未对C ++版本进行基准测试,但是我认为使用标准库数据结构可能会获得比我编写的性能更高的性能。我相信用C ++编写库可以带来巨大的好处,而性能成本可能很小。我尚未对C ++版本进行基准测试,但是我认为使用标准库数据结构可能会获得比我编写的性能更高的性能。我相信用C ++编写库可以带来巨大的好处,而性能成本可能很小。我尚未对C ++版本进行基准测试,但是我认为使用标准库数据结构可能会获得比我编写的性能更高的性能。


9
最新的MSVC支持实际上是C89。
2014年

4
@detly 在Visual Studio 2013中,支持绝大多数C99功能。它不是完全支持,但我会在实践中押注,可以用它来编写C99。
congusbongus 2014年

4
@ danielu13-您究竟在哪里听说C比C ++具有性能优势?
拉姆猎犬,2014年

1
@Sebastian-LaurenţiuPlesciuc我认为这些链接实际上没有帮助。第一个可以用几乎相同的问题得到很好的反击programmers.stackexchange.com/q/113295/76444但赞成C ++而不是C就像在你的链接。对于您的第二个链接,这只是一团莱纳斯·托弗尔德。我希望每个人现在都知道他真的很喜欢讨厌c ++,不会轻易动手,但是他对c ++的抱怨几乎没有客观性,他们充满个人见解和偏见,并不能真正反映语言的真实性。 。至少那是我的看法
user1942027 2014年

1
@RaphaelMiedl我还提到这是在很久以前的2007年编写的,从那时起,C ++编译器和C ++语言得到了发展。无论如何,由程序员决定使用哪种语言。
塞巴斯蒂安·劳伦·普莱西乌克

Answers:


89

我想人们经常声称C是比C ++更快,因为它更容易的原因有关C. C ++的表现是不是天生慢或更快,但某些C ++代码可能会掩盖隐藏的性能损失。例如,当查看某些C ++代码时,可能会有一些副本和隐式转换不立即可见。

我们来看以下语句:

foo->doSomething(a + 5, *c);

让我们进一步假设它doSomething具有以下签名:

void doSomething(int a, long b);

现在,让我们尝试分析该特定语句可能对性能产生的影响。

在C语言中,含义非常清楚。foo只能是指向结构的指针,并且doSomething必须是指向函数的指针。*c取消引用long,并且a + 5是整数加法。唯一的不确定性来自的类型a:如果它不是整数,则将进行一些转换。但是除此之外,很容易量化这条语句对性能的影响。

现在让我们切换到C ++。现在,同一条语句可以具有非常不同的性能特征:

  1. doSomething可能是非虚拟成员函数(便宜),虚拟成员函数(有点贵)std::function,,lambda等。更糟糕的是,foo可能是operator->带有某种未知复杂性的类的重载。因此,为了量化通话的成本doSomething,它现在需要知道的确切性质foodoSomething
  2. a可以是整数,也可以是对整数的引用(其他间接寻址),也可以是实现的类类型operator+(int)。运算符甚至可以返回可以隐式转换为的另一个类类型int。同样,仅从陈述中并不能看出性能成本。
  3. c可能是实现的类类型operator*()。它也可以是对long*等的引用。

您得到图片。由于C ++的语言特性,它是非常难以量化的单个语句的性能成本比在现在C.此外,抽象,如std::vectorstd::string用C常用++,它有自己的性能特点,以及隐藏动态内存分配(另请参阅@Ian的答案)。

因此,底线是:通常,使用C或C ++可获得的可能性能没有区别。但是对于真正的性能至关重要的代码,人们通常更喜欢使用C,因为这样可以减少潜在的性能损失。


1
极好的答案。这就是我在回答中提到的内容,但是您对它的解释要好得多。
伊恩·戈德比

4
这确实应该是公认的答案。它解释了为什么存在诸如“ C比C ++更快”之类的语句。C可以比C ++更快或更慢,但是通常更容易弄清楚为什么特定的C代码片段是快/慢,这通常也使得优化变得更容易。
狮子座

不要从这个出色的答案(我给我+1)中拿走任何东西,但是在这个比较中,编译器可以是苹果和橘子。它/它们可能会为C和C ++生成相同的代码,也可能不会。当然,即使使用相同的源语言假设编译物理上相同的程序,对于任何两个编译器或编译器选项也可以说相同。
JRobert

4
我要补充的是,与等效的C运行时相比,大多数C ++运行时都是庞大的,如果内存受限,这将很重要。
詹姆斯·安德森

@JamesAnderson如果你真的内存的限制,你可能不会在所有:)需要一个运行时
纳文

30

对于某些类型的任务,用C ++编写的代码可能比用C编写的代码更快。

如果您喜欢C ++,请使用C ++。与您的软件的算法决策相比,任何性能问题都无关紧要。


6
它可以更快,但出于相同的原因可能不会更快。
罗布

您能举一些用C ++编写的优化代码比优化C更快的示例吗?

1
@TomDworzanski:一个例子是,通过使用模板,可以在编译时确定有关代码路径的决定,并最终将其硬编码到最终二进制文件中,而不是使用c语言编写时所需要的条件和分支以及功能。避免通过内联函数调用。
whatsisname 2016年

23

C ++的设计原则之一是,您不必为不使用的功能付费。因此,如果您使用C ++编写代码并避免使用C中不存在的功能,那么生成的编译代码的性能应相同(尽管您必须对此进行衡量)。

例如,与结构和大量相关函数相比,使用类的成本可以忽略不计。虚拟功能将花费更多,您必须测量性能以查看它是否对您的应用程序重要。其他任何C ++语言功能也是如此。


3
虚拟函数的分配开销几乎可以忽略不计,除非您过分地分解和虚拟化这些东西。与其余代码和数据相比,vtable会很小,并且通过vtable的索引分支会为每个例行调用增加一些时钟。鉴于所有的常规调用(包括返回调用)将在几百到几百万个时钟之间,vtable分支将被埋在本底噪声中。
约翰·斯特罗姆

6
结构是C ++中的类。
2014年

2
@rightfold:可以,但是您仍然可以编写C ++代码,以在不使用this指针语言功能的情况下将指针传递给结构。我就是这么说
格雷格·休吉尔

4
@John真正的代价不是间接的(尽管我很确定这也会与某些处理器的预取有所关系),但事实是您不能内联虚拟函数(至少在C ++中),这会导致许多其他可能优化。是的,这可能会对性能产生巨大影响。
Voo

2
@Voo公平地说,等效的C代码(手动模拟运行时多态性的代码)也可以这样说。最大的区别在于,我相信编译器可以更轻松地确定是否可以在C ++中内联所述函数。
托马斯·爱丁

14

较高级语言有时会变慢的原因之一是,与低级语言相比,它们可以在后台隐藏更多的内存管理。

任何抽象出低级细节的语言(或库,API等)都可能隐藏昂贵的操作。例如,在某些语言中,仅从字符串中修剪尾随空白会导致内存分配和字符串的副本。如果内存分配和复制在一个紧密的循环中反复发生,则尤其会变得昂贵。

如果您使用C编写此类代码,这将是显而易见的。在C ++中可能不是这样,因为分配和复制可以抽象到某个地方的类中。它们甚至可能隐藏在看上去无辜的重载运算符或复制构造函数之后。

因此,如果需要,请使用C ++。但是,当您不知道抽象背后的含义时,不要被抽象的表面便利所迷住。

当然,使用探查器可以找出导致代码运行缓慢的真正原因。


5

对于它的价值,我倾向于使用C ++ 11编写用于增强功能集的库。我喜欢能够利用诸如共享指针,异常,通用编程和其他仅C ++功能的优势。我喜欢C ++ 11,因为我发现我关心的所有平台都支持它。Visual Studio 2013具有许多核心语言功能和库实现可供使用,并且据说正在努力添加其余部分。众所周知,Clang和GCC都支持整个功能集。

话虽如此,我最近读到了一个关于库开发的非常好的策略,我认为这与您的查询直接相关。本文的标题为“与C ++异常配合使用的AC错误处理风格”, Stefanu Du Toit将此策略称为“沙漏”模式。文章的第一段:

我使用所谓的“沙漏”模式编写了很多库代码:我实现了一个库(在我的情况下,通常使用C ++),将其包装在C API中,这成为该库的唯一入口点,然后用C ++或其他某种语言包装该C API,以提供丰富的抽象和方便的语法。当涉及到本机跨平台代码时,C API提供了无与伦比的ABI稳定性,并通过FFI移植到其他语言。我什至将API限制为C的子集,我知道该子集可移植到各种FFI中,并且使库避免泄漏内部数据结构中的更改-希望在以后的博客文章中进一步介绍。


现在解决您的主要问题:性能。

与其他许多答案一样,我建议使用两种语言编写代码在性能方面也一样有效。从个人的角度来看,由于语言特性,我发现用C ++编写正确的代码更加容易,但是我认为这是个人喜好。无论哪种方式,编译器都非常聪明,并且无论如何都倾向于编写比您更好的代码。也就是说,编译器可能会比您更好地优化代码。

我知道很多程序员都这么说,但是您应该做的第一件事是编写代码,然后对其进行概要分析,并在概要分析器建议的地方进行优化。一旦发现瓶颈所在,您将花费更多时间来制作功能并进行优化。


现在,请阅读一些有趣的文章,了解语言功能和优化如何真正以您的喜好工作:

std :: unique_ptr的开销为零

constexp允许编译时计算

移动语义防止不必要的临时对象


std::unique_ptr has zero overhead从技术上来讲,这可能不是正确的,因为如果堆栈由于异常而展开,则必须调用其构造函数。原始指针没有此开销,并且如果您的代码可能不会抛出,则仍然是正确的。一般情况下,编译器将无法证明这一点。
托马斯·爱丁

2
@ThomasEding我指的是与无异常代码有关的大小和运行时开销。如果我错了,请纠正我,但是有些执行模型在未引发异常时会导致零运行时开销,这些模型仍允许在必要时传播异常。即使这样,什么时候可以在的构造函数中抛出异常unique_ptr?它是声明的noexcept,因此至少可以处理所有异常,但是我无法想象首先会抛出什么类型的异常。
vmrob

vmrob:对不起,我的意思是写“ destructor”而不是“ constructor”。我也打算写“证明不会扔”。eek!
托马斯·爱丁

2
@ThomasEding你知道,即使析构函数抛出异常,我也不认为这很重要。只要析构函数不引入任何新的异常,它的销毁开销仍然为零。而且,我相信整个析构函数都可以通过优化内联到单个delete / free调用中。
vmrob

4

严格来说,C ++和C之间的性能差异并不是由于语言中的任何内容,而是因为它会诱使您执行此操作。就像信用卡与现金。它不会使您花费更多,但无论如何您还是会做,除非您非常有纪律。

这是一个用C ++编写的程序的示例,然后对该程序进行了积极的性能调整。无论语言如何,您都需要知道如何进行积极的性能调整。我使用的方法是随机暂停,如本视频所示。

C ++诱使您执行的代价昂贵的事情是过多的内存管理,通知式编程,将程序计数器与多层抽象库信任(如@Ian所说),缓慢隐藏等。


2

如果您使用两种语言进行相同的操作,则与C ++相比,C没有任何性能优势。您可以采用任何体面的C程序员编写的旧C代码并将其转换为有效且等效的C ++代码,它们将以同样快的速度运行(除非您和您的编译器都知道“ restrict”关键字的作用,并且您可以有效地使用它,但大多数人没有)。

如果(1)使用标准C ++库执行无需使用该库就可以更快,更轻松地完成的工作,或者(2)如果使用标准C ++库,则C ++的性能可能会不同,无论变慢还是变快。比在不良C语言中重新实现该库要容易和快捷得多。


1
这似乎并没有提供超过6个先前答案中所解释的任何实质内容
t

我认为这个答案提到了一个没有人提及的重要观点。乍看之下,C ++似乎是C的超集,因此,如果您可以编写快速的C实现,则应该能够编写等效的C ++实现。但是,C99支持strict关键字,该关键字可以避免意外的指针别名。C ++没有这种支持。避免指针混叠的能力是Fortran的重要功能,这使其对高性能应用程序很有用。我希望在类似的领域中也有可能从C99中获得比C ++更好的性能。
user27539
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.