为什么C这么快,为什么其他语言没有那么快?[关闭]


208

在收听StackOverflow播客时,戳记不断出现,“真正的程序员”用C编写,而且C快得多,因为它“靠近机器”。将前一个断言放到另一篇文章中,C有什么特别之处,它可以使其比其他语言更快?或换种说法:是什么阻止其他语言能够编译为与C一样快的二进制文件?


6
您能列出哪个特定节目谈论过吗?我很想听听。
Giovanni Galbo,2009年

2
对于这个问题的回答真是太惊讶了(大多数答案都忽略了编译语言和解释语言之间的根本区别,我知道JIT yada yada yada),以及有多少人正在“捍卫”他们偏爱的语言(FORTRAN男孩需要服药)。
蒂姆·

不要忘记汇编语言。没有什么比组装后的exe更快或更紧凑的了。汇编几乎是纯二进制的,因此它是最快的语言。
KKZiomek '16

3
C是最快的,因为它是光速和相对性?
1998年

当然,C是最快的程序语言是错误的。没有任何一种程序语言能够接近FORTH的速度。FORTH用于触发核弹,它是大多数卫星上的程序语言,是国际空间站以及CERN和ITER的主要程序语言。我正在比较Microsoft C(不同版本)和FORTH之间的速度。YAWN到C ...
Scoobeedo酷

Answers:


200

C没什么特别的。这就是为什么它很快的原因之一。

较新的语言支持垃圾回收动态类型化和其他功能,使程序员可以更轻松地编写程序。

问题是,存在额外的处理开销,这将降低应用程序的性能。C没有任何这些,这意味着没有开销,但是这意味着程序员需要能够分配内存并释放它们以防止内存泄漏,并且必须处理变量的静态类型。

就是说,许多语言和平台,例如Java(带有Java虚拟机)和.NET(带有公共语言运行时),随着诸如即时编译等技术的出现,多年来已经出现了改进,即时编译可以从本地生成本地机器代码。字节码以实现更高的性能。


3
垃圾回收的速度可能比手动内存管理(对于短期程序和/或大量内存)要快。GC允许简单而快速的分配,程序无需花费时间来分配东西。
Kornel

2
C程序通常根据需要分配和取消分配内存。这是低效的。一个好的虚拟机将大量分配和解除分配,从而在许多情况下提高性能。
skaffman

60
除了“硬”之外,没有什么可以阻止C程序执行相同的分块分配和垃圾回收。
短暂

说得很好,但是就像Rob Allen所说的那样,C还提供了比Java或.NET更少的抽象,从而减少了翻译(由于您所说的即时(JIT)编译,如今这种翻译的真实性也越来越差)
Gab Royer

5
正确使用色情内容,手动管理和合理的分配将始终胜过任何GC系统,并且给予了极大的关注,您对使用模式有绝对的了解,GC则不然,再加上GC系统会增加开销
Ion Todirel 2013年

89

C设计师需要权衡取舍。也就是说,他们决定将速度置于安全之上。C不会

  • 检查数组索引范围
  • 检查未初始化的变量值
  • 检查内存泄漏
  • 检查空指针取消引用

当您索引数组时,在Java中,它需要在虚拟机中进行一些方法调用,绑定检查和其他健全性检查。这是正确且绝对正确的,因为这样可以增加安全性。但是在C语言中,即使是非常琐碎的事情也没有放在安全性中。例如,C不需要memcpy检查要复制的区域是否重叠。它不是设计用于编写大型业务应用程序的语言。

但是这些设计决策并不是C语言中的错误。它们是设计使然,因为它允许编译器和库编写器从计算机中获得所有性能。这是C的精神,C Rationale文档如何解释它:

C代码可以是不可移植的。尽管尽力为程序员提供编写真正可移植程序的机会,但委员会不想强迫程序员进行可移植的编写,以排除使用C作为``高级汇编程序''的能力:即编写特定于机器的功能代码是C的优势之一。

保持C的精神。委员会一直将保留C的传统精神作为主要目标。C的精神有很多方面,但是本质是C语言所基于的基本原则的社区观点。C精神的某些方面可以概括为

  • 相信程序员。
  • 不要阻止程序员去做需要做的事情。
  • 保持语言小而简单。
  • 仅提供一种执行操作的方法。
  • 即使不能保证可移植,也要使其快速。

最后的谚语需要一点解释。高效代码生成的潜力是C语言最重要的优势之一。为了帮助确保看似非常简单的操作不会发生代码爆炸,许多操作被定义为目标计算机硬件的工作方式,而不是通过一般的抽象规则。这种愿意忍受机器所做的事情的例子可以从控制用于表达式的char对象扩展的规则中看出:char对象的值是扩展为有符号数还是无符号数通常取决于哪个字节操作更多在目标机器上高效。


51
C确实通过猛烈崩溃来检查空指针解引用:-)。有时还会通过破坏堆栈帧和数据来检查超出范围的数组索引和未初始化的变量。不幸的是,它会在运行时检查这些。
paxdiablo

18
我不会说C是不安全的,这听起来像您在暗示什么。它假定您不是白痴。如果您用枪指着自己的脚射击,那么C会很乐意让您这样做,因为C假定您比以前更聪明。那不一定是坏事。
鲍勃·萨默斯

19
@Bob:是的。说C并不安全,因为它会让您做危险的事情,就像说汽车不安全,因为它会让您开车驶下悬崖。C和开车的人一样安全(但是那里有很多不安全的驾驶员)。
罗伯特·格兰伯

5
鲍勃(Bob),犯缓冲区溢出之类的错误并不意味着您是个白痴。这只是意味着您仍然是人类。我意识到C和C ++ 不错(我非常喜欢它们)。
Johannes Schaub-litb

4
@ JohannesSchaub-litb C非常适合大规模应用程序编程。如果发现很难使项目变得比Hello World大,那么问题出在程序员,而不是语言...

75

如果您花一个月的时间用0.05秒在C中构建某些东西,而我花一天的时间用Java编写相同的东西,并且它在0.10秒内运行,那么C真的更快吗?

但是要回答您的问题,编写良好的 C代码通常会比其他语言编写良好的代码运行更快,因为编写“良好”的C代码的一部分包括在接近机器级别进行手动优化。

尽管编译器确实非常聪明,但是他们还不能创造性地提出与手动算法竞争的代码(假设“手”属于优秀的 C程序员)。

编辑:

很多评论都遵循“我用C语言编写,我不考虑优化”的思路。

但是以这篇文章为例:

在Delphi中,我可以这样写:

function RemoveAllAFromB(a, b: string): string;
var
  before, after :string;
begin
  Result := b;
  if 0 < Pos(a,b) then begin
    before := Copy(b,1,Pos(a,b)-Length(a));
    after := Copy(b,Pos(a,b)+Length(a),Length(b));
    Result := before + after;
    Result := RemoveAllAFromB(a,Result);  //recursive
  end;
end;

并在CI中写下:

char *s1, *s2, *result; /* original strings and the result string */
int len1, len2; /* lengths of the strings */
for (i = 0; i < len1; i++) {
   for (j = 0; j < len2; j++) {
     if (s1[i] == s2[j]) {
       break;
     }
   }
   if (j == len2) {  /* s1[i] is not found in s2 */
     *result = s1[i]; 
     result++; /* assuming your result array is long enough */
   }
}

但是C版本有多少优化?我们在实现方面做出了很多决策,而我在Delphi版本中没有考虑过。字符串如何实现?在Delphi中,我看不到它。在C语言中,我确定它将是指向ASCII整数数组的指针,我们将其称为chars。在C语言中,我们一次测试一个字符是否存在。在Delphi中,我使用Pos。

这只是一个小例子。在大型程序中,C程序员必须每隔几行代码就做出这类低级决策。它加起来是一个手工制作,经过手工优化的可执行文件。


45
公平地说,在C语言中花费一个月的时间并没有多少,而在Java中只需花费0.05秒即可执行(例如,小程序)。
dreamlax

12
我已经用C编程多年了,几乎不需要做任何您暗示的优化。我已经将许多程序移植到了C中(主要来自Perl),并且通常看到速度提高了10倍以上,并且显着减少了内存使用量,而没有进行任何手动编码的优化。
罗伯特·格兰伯

1
当然,由于缺少现有的功能,有些事情可能需要花费更长的时间在C中进行编程,因此这是计算机性能和程序员性能之间的权衡(这是其他原因),这就是为什么我们不全部使用C进行所有编程的原因。
罗伯特·格兰伯

4
我创建了C ++程序,用更少的时间处理数千行数据,而Java或.NET程序可以启动。这是我对更现代的语言感到沮丧的原因之一。C非常适合需要最少运行时要求的精益程序。PowerBasic对此也很棒。
bruceatk,2009年

35
您是说用C语言花一个月,而用Java语言编写的只花一天时间的程序快两倍的程序,不值得吗?如果该程序每天需要运行500,000,000次以上怎么办?两倍的速度非常重要。如果它在数千或数百万个CPU上运行,则为获得两倍的性能而在额外的开发月份中节省的成本将是巨大的。基本上,在选择开发平台之前,您必须了解/理解部署的规模。
nicerobot 2014年

49

我还没有看到它,所以我要说: C趋向于更快,因为几乎所有其他内容都是用C编写的

Java是基于C的,Python是基于C(或Java或.NET等)的,Perl等。操作系统是用C编写的,虚拟机是用C编写的,编译器是用C编写的,解释程序是用C编写的。有些东西仍然是用汇编语言编写的,这往往会更快。越来越多的东西正在用其他方式编写,而其他本身就是用C编写的。

您用其他语言(而不是程序集)编写的每个语句通常在C中以多个语句的形式实现,这些语句被编译为本机代码。由于倾向于使用那些其他语言来获得比C更高的抽象级别,因此C中所需的那些额外语句倾向于将重点放在增加安全性,增加复杂性和提供错误处理上。这些通常是好东西,但它们有成本,而且它的名称是速度大小

就我个人而言,我实际上使用了数十种语言,涵盖了大多数可用频谱,并且我个人一直在寻求您所暗示的魔力:

我怎么也可以吃蛋糕?我该如何使用自己喜欢的语言来进行高级抽象处理,然后再考虑C的实质,以提高速度?

经过几年的研究,我的答案是Python(在C上)。您可能想看看。顺便说一句,您也可以从Python下拉到Assembly(在特殊库的帮助下)。

另一方面,错误的代码可以用任何语言编写。因此,C(或汇编)代码不会自动更快。同样,一些优化技巧可以使高级语言代码的某些部分接近原始C的性能水平。但是,对于大多数应用程序,您的程序将大部分时间都花在等待人或硬件上,因此区别实际上并不重要。

请享用。


10
这实际上不适用于JIT编译的语言。并不是说我的C#被编译为IL,然后将其翻译为C,然后将C编译为机器代码。不,IL是JIT编译的-那时JIT的实现语言是无关紧要的。它只是产生机器代码。
乔恩·斯基特

3
上帝禁止我质疑传奇的乔恩·斯凯特(Jon Skeet),但所生成的机器代码是C#而不是C似乎完全相关,因此它是“更高级别的”,具有更多功能,具有安全检查等,并且会是,因此,比“当量” C.慢
罗布威廉姆斯

3
@Jon:我要说类似的话,但是这一点实际上是有道理的,因为许多.NET库核心组件实际上实际上是用C编写的,因此具有C的速度限制。看看将来会如何变化将是很有趣的。
康拉德·鲁道夫

1
这似乎是错误的方法,其他语言编译器/解释器/ vms经常但并非总是用c编写(或至少是针对最低层),因为c相当快(在许多情况下是最快的)。
罗曼·泰切

2
这个答案是不正确的。如上所述,它不适用于JIT语言,但也不适用于具有自己的编译器的语言(如果投入大量精力,它们可能比现代C编译器生成更快的代码)。剩下的唯一其他一类语言是解释语言,它们之所以不会比C慢,不仅是因为它们本身是用C编写的,而是因为解释的开销,无论您如何对其进行分片,即使解释器是用汇编编写的, 很大。
得分_

38

那里有很多问题-大多数是我没有资格回答的问题。但是对于这最后一个:

是什么使其他语言无法编译成可以像C一样快的二进制代码运行?

总之,抽象。

C仅是远离机器语言的一两个抽象级别。Java和.Net语言至少要远离汇编程序3个抽象级别。我不确定Python和Ruby。

通常,程序员玩弄的越多(复杂的数据类型等),您与机器语言的距离就越远,并且需要完成的翻译越多。

我到处都是,但这是基本要点。

更新 -------这篇文章有一些不错的评论,有更多详细信息。


3
从技术上讲,Java和.Net是从运行它们的计算机的机器语言中无限抽象出来的。它们在VM中运行。即使使用JIT,也必须对原始代码进行大量处理,以使它们类似于本机代码。
jmucchiello,2009年

1
.net代码无法在VM中运行。它可以作为本机指令在其运行的任何处理器平台(32位x86、64位x86或IA64)上运行。
罗伯特·巴特

11
@Robert:.net 确实使用了VM。.net代码被编译为由VM执行的字节码。VM在运行时将字节码转换为本地指令。
罗伯特·加伯

3
这是非常值得注意的是Java和其他面向对象语言的概念已影响到处理器的指令集非常重要的。如果Java VM知道并使用了这些优化,则较新的处理器会发出指令,使Java运行更快。它虽然不大,但是很有帮助。
亚当·戴维斯


35

由于C的成本模型是透明的,因此C的速度并不快。如果C程序很慢,那么它显然很慢:通过执行很多语句。与C语言中的操作成本相比,对对象(尤其是反射)或字符串的高级操作所带来的成本并不明显。

通常可以编译为与C一样快的二进制文件的两种语言是Standard ML(使用MLton编译器)和Objective Caml。如果您查看基准测试游戏,您会发现对于某些基准(例如二叉树),OCaml版本比C更快(我没有找到任何MLton条目。)就像它所说的那样,结果通常反映出人们在调整代码上付出了多少努力。


可以用任何语言编写非显而易见的昂贵代码。只是在某些语言中,您必须先编写Lisp或Forth的内部变体…
Donal Fellows 2010年

Rust在基准测试中也匹配C。
鲜明

18

C并不总是更快。

C比例如Modern Fortran慢。

在某些方面,C通常比Java慢。(尤其是在JIT编译器处理完您的代码之后)

C让指针别名发生,这意味着不可能进行良好的优化。特别是当您有多个执行单元时,这会导致数据获取停顿。哎呀

指针算术有效的假设确实会导致某些CPU系列(尤其是PIC)的性能缓慢下降,它曾经在分段x86上占据了很大的份额。

基本上,当您获得向量单元或并行化编译器时,C臭味和现代Fortran运行速度更快。

C程序员的技巧,例如重击(动态修改可执行文件),会导致CPU预取停止。

你漂流了吗?

我们的好朋友x86执行的指令集如今与实际的CPU体系结构关系不大。影子寄存器,负载存储优化器都在CPU中。因此,C接近虚拟金属。真正的金属,英特尔让您看不到。(从历史上看,VLIW CPU有点破产,所以也许还不错。)

如果您在高性能DSP(也许是TI DSP?)上使用C进行编程,则编译器必须做一些棘手的工作才能在多个并行执行单元上展开C。因此,在这种情况下,C与金属不是很接近,但与编译器非常接近,后者将对整个程序进行优化。奇怪的。

最后,某些CPU(www.ajile.com)在硬件中运行Java字节码。C将在该CPU上使用PITA。


1
上一次用C语言编写作呕的时间是什么时候?现代的x86是一个主要RISC设计的接口,但有一点做与VLIW ...
Calyth

7
您的大部分文章都忽略了C99的存在。同样,许多C / C ++编译器都提供C99strict关键字(确保没有指针别名)作为扩展。
Evan Teran

我假设每个人都在遵循/过渡到遵循CWE / SANS前25名,并且避免在C语言中进行新设计。因此,没有C语言的绿色领域,只有C99或什至没有。
Tim Williscroft 09年

2
当c比现代Fortenberry慢时,您能举个例子吗?
亚当

我不确定C编译器是否曾与最佳Fortran编译器竞争得很好。当然,您不想使用FORTRAN 77(更不用说66)编写很多代码了,但是最新的Fortran标准变得越来越令人愉悦。
tfb

11

是什么使其他语言无法编译成可以像C一样快的二进制代码运行?

没有。诸如Java或.NET lang之类的现代语言更注重程序员的工作效率,而不是性能。如今,硬件便宜。此外,以中间表示形式进行编译还会带来很多好处,例如安全性,可移植性等。.NETCLR可以利用不同的硬件-例如,您无需手动优化/重新编译程序即可使用SSE指令集。


我会在这里争论便携性。如果您想要真正可移植的代码,则可以用C而不是任何其他语言编写。我们有在大约25个操作系统上运行的代码。从dos和threadX开始并在Linux / XP上完成,向我展示了另一种可以做到这一点的语言:)
Ilya,2009年

1
@Ilya我不同意。用C语言编写不可移植的代码一样容易。看看将某些端口移植到64位是多么痛苦。如果您具有正确的字节码解释器,则字节码语言可以跨平台工作。
2009年

1
@IIya,可移植的C代码只是一个例子,而不是规则,我在不同的硬件/软件平台之间移植了C代码,并且知道这是一场噩梦。
aku

即使对于PC单词也并非如此。看一下大多数用c / c ++编写的跨平台应用程序,一点点用Java。对于嵌入式低级别开发,没有其他情况了。C实际上是最可移植的语言。
伊利亚(Ilya)

@aku->移植错误的代码可能是灾难,我同意。用ADVANCE-C编写可移植代码是最佳选择。我会说C ++是一种选择,但是进入嵌入式平台,您总会找到不错的C编译器,对于c ++,您可能会发现自己没有编译器。
伊利亚,2009年

8

主要因素是它是一种静态类型的语言,并且已编译为机器代码。另外,由于它是一种低级语言,因此它通常不会做您没有告诉它的任何事情。

这些是我想到的其他一些因素。

  • 变量不会自动初始化
  • 数组无界检查
  • 未经检查的指针操作
  • 没有整数溢出检查
  • 静态类型的变量
  • 函数调用是静态的(除非您使用函数指针)
  • 编译器作者有很多时间来改进优化代码。另外,人们为了获得最佳性能而使用C进行编程,因此存在优化代码的压力。
  • 语言规范的某些部分是实现定义的,因此编译器可以自由地以最佳方式进行操作

不过,大多数静态类型的语言可以比C更快或更快速地编译,尤其是当它们可以假设C由于指针别名等原因而不能编译时。


C低级 我想这是一个相对的意思,与Java相比,但与程序集相比。好帖子,让我思考。
标记

没错,这绝对是相对的。我的意思是说它“靠近机器”,并不能帮助您执行诸如内存管理或跟踪数组大小之类的事情。
马修·克鲁姆利

2
C是一种低级语言。C一直是低级语言。您可以轻松地将C代码手动转换为汇编器。
罗伯特·巴特

2
@Robert:C过去被认为是高级语言,因为与汇编语言相比(这是很常见的)。与当今使用的大多数语言相比,它被认为是一种低级语言。
罗伯特宝洁

老实说,这是一个非常有偏见的答案。该死的几乎所有C程序员都进行边界检查等。然而C仍然比C ++快得多。
MarcusJ '16

8

我猜你忘了汇编语言也是一种语言:)

但是严重的是,仅当程序员知道自己在做什么时,C程序才会更快。您可以轻松地编写一个C程序,该程序的运行速度比用其他语言完成相同任务的程序慢。

C之所以更快,是因为它是按这种方式设计的。它使您可以执行许多“较低级别”的工作,以帮助编译器优化代码。或者,我们应该说,您是程序员负责优化代码。但这通常很棘手且容易出错。

像已经提到的其他语言一样,其他语言则更多地关注程序员的生产力。通常认为,程序员的时间比机器的时间(甚至在过去)要贵得多。因此,最大限度地减少程序员在编写和调试程序上的时间,而不是在程序的运行时间上,具有很大的意义。要做到这一点,您将牺牲一些精力来使程序更快,因为很多事情都是自动化的。


3
尽管如果您用C编写一次程序,然后再用Assembly编写程序,则C版本可能会更快,因为编译器比您更聪明。
mk12 2012年

7

在大多数情况下,每个C指令都对应于很少的汇编程序指令。您实质上是在编写更高级别的机器代码,因此您几乎可以控制处理器的所有工作。许多其他编译语言,例如C ++,具有许多看起来很简单的指令,它们可以转换成比您认为的要多的代码(虚拟函数,副本构造函数等。)而Java或Ruby之类的解释语言则具有另外一层您从未见过的说明-虚拟机或解释器。


这些高级语言中的某些以它们能够消除最初添加的大部分废语为荣。在C ++中,诸如复制省略,返回值优化,移动构造/赋值等操作。
cmaster-恢复莫妮卡

因此,这全部归结为从代码生成的汇编指令的数量。每行高级代码中的汇编指令越多,性能遭受的影响越大?
ENDEESA

这可能过于简单,但是汇编指令的数量与程序速度之间存在直接关系。处理器执行每个指令的时间最少。恕我直言,一种可以让您轻松理解正在编写的指令数量的语言可以帮助您编写更有效的代码。(还有其他处理器时间成本,例如分支和高速缓存未命中,但即使在那儿,较少的抽象也有助于弄清CPU在做什么。)
AShelly


7

C ++的平均速度要快一些(尽管最初是C的超集,尽管存在一些差异)。但是,对于特定的基准测试,通常存在另一种更快的语言。

https://benchmarksgame-team.pages.debian.net/benchmarksgame/

fannjuch-redux 在Scala最快

n-body并且fasta在Ada更快。

spectral-norm 在Fortran中最快。

reverse-complementmandelbrot并且pidigits在ATS中速度最快。

regex-dna 在JavaScript中是最快的。

chameneou-redux 最快的是Java 7。

thread-ring 在Haskell最快。

其余基准测试在C或C ++中最快。


“因为它是一个超级组C” -没有,C ++是 C.的超集
PP

1
extern "C"无关++做其中C为一个超集 C的
PP

2
这就像说system("bash script.sh");适用于任何bash脚本一样,因此C是bash的超集。extern "C"由于名称修改,在C ++中提供了C链接。而将X称为Y的超集意味着将所有可以在Y中完成的事情也可以在X中完成,而C ++并非如此。有相当多的语言构造在C中有效,但在C ++中无效。
PP

1
@PP在C ++标准中,没有什么要求bash可以使用命令行程序。如果支持并且包括需要支持的bash版本/规范,我将认为它是超集。
彼得·劳瑞

1
编写非C ++代码的C代码非常容易,例如struct foo { int: this; }; typedef float foo;
Jasen

6

这些答案中的许多都给出了为什么C更快或更慢(无论是在一般情况还是在特定情况下)的有效理由。不可否认的是:

  • 许多其他语言提供了我们认为理所当然的自动功能。例如,边界检查,运行时类型检查和自动内存管理不是免费提供的。这些功能至少有一些成本,在编写使用这些功能的代码时,我们可能不会考虑甚至意识到。
  • 在其他语言中,从源代码到机器的步骤通常不像在C语言中那样直接。
  • OTOH可以说,已编译的C代码的执行速度比用其他语言编写的其他代码要快,这是一种概括,但并非总是如此。反例很容易找到(或尝试)。

尽管如此,我认为还有其他一些事情,我认为它比其他任何因素对C语言与许多其他语言的比较性能的影响更大。以机智:

其他语言通常使编写执行速度较慢的代码更容易。通常,它甚至受到语言的设计哲学的鼓舞。结论:C程序员更有可能编写不会执行不必​​要操作的代码。

例如,考虑一个简单的Windows程序,其中创建了一个主窗口。AC版本将填充WNDCLASS[EX]要传递给的结构RegisterClass[Ex],然后调用CreateWindow[Ex]并进入消息循环。高度简化和缩写的代码如下:

WNDCLASS wc;
MSG      msg;

wc.style         = 0;
wc.lpfnWndProc   = &WndProc;
wc.cbClsExtra    = 0;
wc.cbWndExtra    = 0;
wc.hInstance     = hInstance;
wc.hIcon         = NULL;
wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wc.lpszMenuName  = NULL;
wc.lpszClassName = "MainWndCls";

RegisterClass(&wc);

CreateWindow("MainWndCls", "", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
             CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

while(GetMessage(&msg, NULL, 0, 0)){
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

C#中的等效程序可能只是一行代码:

Application.Run(new Form());

这一行代码提供了将近20行C代码所具有的全部功能,并增加了一些我们遗漏的内容,例如错误检查。较丰富,更完整的库(与典型C项目中使用的库相比)为我们做了很多工作,使我们腾出时间来编写更多的代码片段,这些代码片段对我们来说似乎很短,但是涉及幕后的许多步骤。

但是,能够轻松实现快速代码膨胀的功能丰富的库并不是我真正的观点。当您开始研究当我们的小单线实际执行时实际发生的情况时,我的观点更加明显。有时,请在Visual Studio 2008或更高版本中启用.NET源代码访问,并跳入上面的简单一行代码。您会遇到的有趣的小宝石之一是getter中的以下评论Control.CreateParams

// In a typical control this is accessed ten times to create and show a control.
// It is a net memory savings, then, to maintain a copy on control.
// 
if (createParams == null) {
    createParams = new CreateParams(); 
} 

十次。从类中检索大约等于WNDCLASSEX结构中存储的内容和传递给什么的总和的信息,然后再将其存储在结构中并传递给and 十次。CreateWindowExControlWNDCLASSEXRegisterClassExCreateWindowEx

总而言之,在C#中执行该基本任务所执行的指令数量比在C中要多2–3个数量级。部分原因是由于使用了功能丰富的库,该库必须是通用的,而与我们的简单C代码完全可以满足我们的需求,仅此而已。但是,部分原因是由于.NET框架的模块化,面向对象的性质使自己能够执行很多重复执行,而这通常可以通过过程方法来避免。

我不是在尝试使用C#或.NET框架。我也不是说模块化,泛化,库/语言功能,OOP等都是不好的事情。我曾经在C,后来的C ++和最近的C#中完成大部分开发工作。同样,在使用C之前,我主要使用汇编语言。随着我的语言“一步一步”发展,我可以在更短的时间内编写出更好,更可维护,更强大的程序。但是,它们的执行速度往往会慢一些。


2
那是API问题,而不是语言问题。
Arafangion

1
@Arafangion:我理解你在说什么,但这有点不对。功能丰富的库由功能丰富的语言启用(并且以某种方式要求)。不只是图书馆。该库只是该语言常用用法的一个示例。任何语言中的典型应用程序代码通常与该语言中通常使用的库相似。实际上,这更是一种语言所培养的思维定势。例如,与OOP支持较少的语言相比,OO语言通常花费更多的时间来分配,构造,销毁和取消分配对象。
P爸爸

我承认,给定的语言选择通常意味着特定的平台和库,这就是为什么我要做出这样的评论(以便读者更加了解),但是也就是说,在Windows上使用(例如)C ++是一种例如,与Linux上的C ++完全不同的野兽,与Android上的C ++完全不同。另一个例子是Python-我们有CPython,Jython,PyPy和IronPython-所有这些都使用非常不同的库。
Arafangion 2010年

但是使用这些Python中的任何一个,开发人员都倾向于以某种方式编写应用程序。例如,他们可以从文本文件读取和写入,并使用读取的数据创建新对象。另一方面,在C语言中,开发人员更可能一次性分配结构数组,并从二进制文件读取和写入这些结构。当然,这只是一个人为的例子,试图说明我要提出的关于心态的观点。
P爸爸

6

我认为没有人提到C编译器比Java编译器付出了更多的努力这一事实。

由于已经说明的许多原因,C可以进行极度优化-比几乎任何其他语言都多。因此,如果其他语言编译器投入相同的精力,C可能仍然会排在首位。

我认为至少有一种候选语言可以比C更好地进行优化,因此我们可以看到产生更快二进制文件的实现。我之所以想到数字火星D,是因为创建者非常谨慎地构建了一种可能比C更好地优化的语言。可能还有其他语言也有这种可能性。但是我无法想象,任何一种语言的编译器都将比最好的C编译器快百分之几。我想错了。

我认为真正的“垂头丧气的果实”将使用旨在方便人类优化的语言。熟练的程序员可以使任何语言的运行速度更快-但有时您必须做一些荒谬的事情或使用不自然的结构来实现这一目标。尽管会一直努力,但是一种好的语言应该产生相对快速的代码,而不必沉迷于程序的确切编写方式。

同样重要的是(至少对我而言),最坏情况的代码往往很快。Web上有许多“证明”,证明Java比C快或快,但这是基于“摘樱桃”的例子。我不是C的忠实拥护者,但我知道我用C编写的所有内容都将运行良好。使用Java,它将“大概”以15%的速度运行,通常在25%的速度内运行,但是在某些情况下,运行速度可能会更差。在任何情况下,它的速度一样快或在百分之几之内通常是由于大部分时间都花在了经过大量优化的C语言的库代码中。


5

这实际上是一种永久的虚假。虽然确实C语言程序通常会更快,但并非总是如此,特别是如果C程序员不太擅长于此。

人们往往会忘记的一个明显的大漏洞是程序必须阻止某种类型的IO,例如任何GUI程序中的用户输入。在这些情况下,使用哪种语言并不重要,因为您受到输入数据的速度的限制,而不是处理数据的速度限制。在这种情况下,使用C,Java,C#甚至Perl都没关系。您只是走得比数据输入快得多。

另一个主要问题是,使用垃圾回收而不使用适当的指针可使虚拟机进行许多其他语言无法提供的优化。例如,JVM能够在堆上移动对象以对其进行碎片整理。由于下一个索引可以简单地使用而不是在表中查找,因此这使得将来的分配要快得多。现代的JVM也不必实际释放内存。取而代之的是,它们只是在GC时移动活动对象,而从死对象中耗尽的内存基本上是免费恢复的。

这也带来了有关C的有趣观点,甚至在C ++中也是如此。一种设计理念是“如果不需要它,就不用付钱”。问题是,如果您确实想要它,您最终将为此付出代价。例如,Java中的vtable实现往往比C ++实现要好得多,因此虚拟函数调用要快得多。另一方面,您别无选择,只能在Java中使用虚拟函数,它们仍然会花费一些钱,但是在使用大量虚拟函数的程序中,降低的成本加起来了。


1
“ Java中的vtable实现往往比C ++实现要好得多,因此虚拟函数调用要快得多。” 您到底能比MOV EAX [ECX]快得多吗?致电[EAX + someindex]; ?除非您可以在不查找的情况下调用函数,否则这看起来非常理想。
反式威廉

@Frans-JIT编译器(例如Java HotSpot)可以在确定给定对象始终为给定类型的情况下内联vtable查找。如果C ++在编译时知道相同的信息,它也将执行此操作,但是使用Java字节码进行此优化比使用x86机器指令更容易进行此优化。
汤姆

6
@James-争论“ I / O降低性能的重要性”不会使“ C比其他语言更快”的声明无效。那不是一个明显的漏洞,而是一个稻草人的论点。
汤姆(Tom)” 2009年

最好使用C的字符串处理(以及标准C库的)作为示例,因为那是C较差的地方。即使使用简单的起始代码,大多数其他语言也会做得更好。
Donal Fellows 2010年

@DonalFellows在某些任务上,mem *函数的速度可能比str *函数的速度快,但是如果小心一点,字符串处理会很有效。您有特定的基准吗?
杰森

4

与其说语言,不如说是工具和库。C可用的库和编译器比新语言要老得多。您可能会认为这会使它们变慢,但很不利。

这些库是在处理能力和内存非常宝贵的时候编写的。为了工作,必须非常高效地编写它们。C编译器的开发人员也有很长时间从事针对不同处理器的各种聪明的优化工作。C的成熟度和广泛采用使其比同年龄的其他语言有明显的优势。与较不强调原始性能的新型工具相比,它也使C具有速度优势。


4

缺乏抽象是使C更快的原因。如果编写输出语句,您将确切知道发生了什么。如果您用Java编写输出语句,它将被编译成一个类文件,然后在引入抽象层的虚拟机上运行。缺少面向对象功能作为语言的一部分,这也提高了它的速度,以减少生成的代码。如果您使用C作为一种面向对象的语言,那么您将对所有类(例如类,骚扰等)进行编码。这意味着,然后对每个人进行足够通用的代码量和性能要求,只需要编写即可您需要完成的工作。


4

惊奇地看到旧的“ C / C ++ 必须比Java更快,因为Java被解释了”的神话仍然存在。有几年的文章以及最近的文章用概念或度量解释了为什么并非总是这样的原因

当前的虚拟机实现(顺便说一句,不仅仅是JVM)可以利用程序执行过程中收集的信息,通过多种技术动态地调整代码的运行状态:

  • 渲染频繁的机器代码方法,
  • 内联小方法,
  • 锁定调整

以及其他各种调整,以了解代码实际在做什么以及运行代码的环境的实际特征为基础。


我同意Java在过去几年中已经取得了显着的性能改进,使其在原始性能方面与C更加接近,但是要弄清它这么长时间这么慢的事实还需要一段时间。但是反正谁在谈论Java?
罗伯特·甘布尔

Java是OP引用的“其他语言”,不是吗?
罗伯特·巴特

@Robert:“其他语言”,复数形式,除了C之外没有提及任何其他特定语言。您如何从中读取“ Java”?
罗伯特·

@Roberd:在遇到这个问题之前,我已经发布了几个答案,它们都在谈论Java(或其他通常通过解释器或VM实现的语言)。
joel.neely,2009年

3
@Joel-如果您知道目标硬件,则可以在C或C ++中使用配置文件引导的优化来完成JVM在运行时可以执行的大多数优化。这产生了巨大的差异,并且通常将C和C ++推回了领先地位,因为它们不必在执行时“学习”。
汤姆(Tom)” 2009年

4

运行最快的代码是精心制作的机器代码。汇编程序将几乎一样好。两者都是非常低的级别,并且需要花费大量的代码来编写代码。C比汇编程序高一点。您仍然可以在实际的计算机中以非常低的级别控制事物,但是有足够的抽象性使其比汇编器更快速,更轻松地编写代码。其他语言(例如C#和JAVA)则更为抽象。汇编程序和机器代码称为低级语言,而C#和JAVA(以及许多其他语言)称为高级语言。C有时被称为中级语言。


在阅读您的答案时,整段中只有两个词吸引着我,就像吸引人的金属一样。在该段中,单词是JAVA,两次。从来没有见过它写在全部大写之前,它看起来不错:-)
Sнаđошƒаӽ

3

不要用别人的话来理解,而是在代码的任何性能关键部分中都使用C语言和您选择的语言进行反汇编。我认为您只需在运行时在Visual Studio中的反汇编窗口中查看反汇编的.Net。如果使用windbg对于Java来说比较棘手,应该可以,尽管如果使用.Net进行操作,则许多问题都是相同的。

我不想在不需要的情况下用C编写代码,但是我认为这些答案中的许多主张都可以通过简单地在C和C语言中分解相同的例程来抛弃C以外的语言的速度。使用您选择的高级语言,尤其是在涉及大量数据的情况下(如性能关键型应用程序中常见的情况)。不知道,Fortran在其专业领域可能是个例外。它比C高吗?

我第一次将JITed代码与本机代码进行了比较,从而解决了.NET代码是否可以与C代码相比运行的所有问题。额外的抽象级别和所有安全检查会带来巨大的成本。同样的成本可能适用于Java,但不要相信我,而是在性能至关重要的方面尝试使用。(任何人都对JITed Java足够了解,可以在内存中找到已编译的过程吗?这肯定有可能)


2

1)正如其他人所说,C为您做的更少。没有初始化变量,没有数组边界检查,没有内存管理等。其他语言中的这些功能消耗了C不会花费的内存和CPU周期。

2)回答说,C的抽象程度较低,因此速度较快,我认为只有一半正确。从技术上讲,如果您有一个用于语言X的“足够高级的编译器”,那么语言X可以达到或等于C的速度。与C的不同之处在于,由于C的映射非常明显(如果您学习过架构课程),并且直接使用汇编语言,即使是天真的编译器也可以做得不错。对于像Python这样的东西,您需要一个非常高级的编译器来预测对象的可能类型并动态生成机器代码-C的语义非常简单,因此简单的编译器就可以很好地完成工作。


2

在过去的美好时光中,只有两种类型的语言:编译和解释。

编译语言利用“编译器”读取语言语法并将其转换为相同的汇编语言代码,而不仅仅是直接在CPU上。解释的语言使用了两种不同的方案,但实际上语言语法已转换为中间形式,然后在“解释器”(用于执行代码的环境)中运行。

因此,从某种意义上说,在代码和机器之间还有另一个“层”(解释器)。而且,像在计算机中一样,更多意味着更多的资源被使用。口译员比较慢,因为他们必须执行更多操作。

最近,我们看到了更多的混合语言,例如Java,它们同时使用编译器和解释器来使它们工作。它很复杂,但是JVM比旧的解释器更快,更复杂,更优化,因此(随着时间的推移)它的性能变化要好得多,接近于直接编译的代码。当然,较新的编译器还具有更多出色的优化技巧,因此它们倾向于生成比以前更好的代码。但是大多数优化通常会(尽管并非总是)进行某种权衡取舍,因此并非在所有情况下都总是较快。像其他所有东西一样,没有什么是免费的,因此优化器必须从某个地方夸耀自己(尽管经常使用编译时CPU来节省运行时CPU)。

回到C,这是一种简单的语言,可以将其编译为相当优化的程序集,然后直接在目标计算机上运行。在C语言中,如果增加一个整数,很有可能只是CPU中的一个汇编程序步骤,但是在Java语言中,它最终可能会比这更多(并且还可能包含一些垃圾回收: -)C为您提供了一个更接近机器的抽象(汇编程序是最接近的),但是您最终不得不做更多的工作才能使它运行,并且它没有那么受保护,易于使用或对错误友好。大多数其他语言为您提供了更高的抽象度,并为您提供了更多的底层细节,但是作为其高级功能的交换,它们需要更多的资源来运行。当您概括一些解决方案时,您必须处理更广泛的计算,

保罗


“在C语言中,如果增加一个整数,则很有可能只是CPU中的一个汇编程序步骤。”并非完全正确。如果该整数不在CPU寄存器中,则需要具有机器码才能从内存中获取它,对其进行递增,然后将其写回到内存中。运行Java代码时,可能会发生大致相同的情况。而且我不知道为什么“ ++ i”会自行触发GC周期。
–quant_dev

@quant_dev:“您有..从内存中获取,递增,写回...”。也许吧,也许不是。例如,x86具有对内存中的数据进行操作的指令。++i可能会编译为“加[ebp-8],1”。并不是说获取,递增,存储仍然没有发生,但这是由CPU处理的,就像Paul所说的,这只是一条指令。
P爸爸2009年

...这与性能的POV仍然无关,因为它可能只是一条指令,但仍涉及等待数据到达CPU。缓存未命中以及所有这些。
quant_dev

不,我不会说这无关紧要。一个CPU指令通常比多个CPU指令占用更少的代码字节,从而在代码段上产生更好的缓存性能。一条CPU指令还占用了CPU管道较少的空间,并且可以通过单独的管道阶段来并行处理多个步骤(获取,递增,存储)。实际上,如果某个操作的某些部分可以与管道上的其他操作融合,则甚至可以跳过它们。例如,可以将存储值与随后加载相同的值合并在一起。
P爸爸2009年

2

抛开诸如热点优化预编译的元算法和各种形式的并行性之类的高级优化技术,语言的基本速度与支持操作通常所需的隐式幕后复杂性密切相关。在内部循环中指定。

也许最明显的是对间接内存引用的有效性检查-例如检查指针是否存在null以及是否针对数组边界检查索引。大多数高级语言都隐式执行这些检查,而C则不。但是,这不一定是这些其他语言的基本限制 -足够聪明的编译器可能能够通过某种形式的循环不变代码运动从算法的内部循环中删除这些检查。

C(以及类似程度的密切相关的C ++)的更根本优势是严重依赖基于堆栈的内存分配,该内存本质上可以快速进行分配,释放和访问。在C(和C ++)中,主调用堆栈可用于分配基元,数组和聚合(struct/ class)。

虽然C确实提供了动态分配任意大小和寿命的内存的功能(使用所谓的“堆”),但是默认情况下会避免这样做(改为使用堆栈)。

令人着迷的是,有时可以在其他编程语言的运行时环境中复制C内存分配策略。asm.js证明了这一点,它允许将用C或C ++编写的代码转换为JavaScript的子集,并可以在Web浏览器环境中安全地运行-速度接近自然。


顺便说一句,C和C ++在速度方面胜过大多数其他语言的另一个领域是与本地机器指令集无缝集成的能力。一个明显的例子是(依赖于编译器和平台)SIMD内部函数的可用性,这些内部函数支持构建自定义算法,该算法利用现在几乎无处不在的并行处理硬件-同时仍利用该语言提供的数据分配抽象(下)级别的寄存器分配由编译器管理)。


1
C的这种内存分配优势中的某些优势也可以由聪明的编译器以其他语言复制(请参见此处)。但是,我得到的印象是,这在结构上很难做到,尤其是对于非原始数据类型。 这篇文章提到了一个非转义对象的想法,该对象可以作为Java中的优化进行堆栈分配。
nobar

2

我在链接上找到了关于为什么某些语言更快而有些慢的链接的答案,我希望这会更清楚地说明为什么C或C ++比其他语言快。还有其他一些语言也比C快,但是我们不能使用所有这些。一些解释-

Fortran仍然很重要的主要原因之一是因为它速度快:用Fortran编写的数字运算例程比用大多数其他语言编写的等效例程要快。之所以使用与Fortran在此领域竞争的语言(C和C ++)是因为它们在性能方面具有竞争力。

这就提出了一个问题:为什么?C ++和Fortran使它们快速运行的原因是什么?为什么它们比其他流行的语言(如Java或Python)好?

解释与编译根据编程语言和所提供功能的不同,可以使用多种方法对编程语言进行分类和定义。在查看性能时,最大的区别是解释语言和编译语言之间的区别。

鸿沟并不难;而是有一个频谱。一方面,我们有传统的编译语言,其中包括Fortran,C和C ++。在这些语言中,存在一个离散的编译阶段,该阶段将程序的源代码转换为处理器可以使用的可执行形式。

此编译过程包含几个步骤。源代码经过分析和解析。此时可以检测到基本的编码错误,如错别字和拼写错误。解析的代码用于生成内存中的表示形式,该表示形式也可以用于检测错误-这次是语义错误,例如调用不存在的函数,或者尝试对文本字符串执行算术运算。

然后,此内存中表示形式用于驱动代码生成器,该代码生成器生成可执行代码。在此过程中的不同时间执行代码优化,以提高生成的代码的性能:可以对代码表示形式执行高级优化,而对代码生成器的输出使用较低级别的优化。

实际执行代码会在稍后发生。整个编译过程仅用于创建可以执行的内容。

在另一端,我们有口译员。解释器将包括一个类似于编译器的解析阶段,但是该解析阶段用于驱动直接执行,并立即运行程序。

最简单的解释器中包含与语言支持的各种功能相对应的可执行代码,因此它将具有添加数字,连接字符串的功能,以及给定语言所具有的其他功能。在解析代码时,它将查找并执行相应的函数。在程序中创建的变量将保留在某种查找表中,该表将其名称映射到其数据。

解释器样式的最极端示例是诸如批处理文件或Shell脚本之类的东西。在这些语言中,可执行代码通常甚至不内置在解释器本身中,而是独立的独立程序。

那么,为什么这会影响性能呢?通常,间接的每一层都会降低性能。例如,将两个数字相加的最快方法是将这两个数字都放在处理器的寄存器中,并使用处理器的加法指令。那就是编译程序可以做的;他们可以将变量放入寄存器中并利用处理器指令。但是在解释程序中,相同的加法可能需要在变量表中进行两次查找以获取要添加的值,然后调用一个函数来执行加法。该函数很可能使用与编译程序用来执行实际加法的处理器指令相同的处理器指令,但是实际上可以使用该指令之前的所有额外工作会使事情变慢。

如果您想了解更多信息,请检查来源


1

某些C ++算法比C快,而其他语言中算法或设计模式的某些实现可能比C快。

当人们说C很快,然后再谈论其他语言时,他们通常以C的性能为基准。


2
“某些C ++算法比C更快”没有任何意义。任何“ C ++算法”都可以用C编写,反之亦然。算法与语言无关。C ++本质上为C添加了功能-这些新功能都不能带来更快的算法(尽管它们可能更容易编写)。

1
经典的斥责是std :: sort算法。std :: sort比C中的任何排序算法都快-在C中获得相同性能的唯一方法是在您想要或使用宏的任何地方对其进行硬编码-甚至然后编译器需要优化的信息较少。
Arafangion

1

使用现代的优化编译器,纯C程序几乎不可能比已编译的.net代码快得多。随着生产力的提高,像.NET框架提供的开发者,你可以做的事情在一天以前需要数周或数月再加上相比,开发人员的薪水硬件成本低廉定期下,它只是WAY便宜写用高级语言填充内容,并以任何慢速投放硬件。

Jeff和Joel之所以说C是“真正的程序员”语言,是因为C中没有人手。您必须分配自己的内存,释放该内存,进行自己的边界检查等。作为new object(); 没有垃圾收集,类,OOP,实体框架,LINQ,属性,属性,字段或类似的东西。您必须了解指针算术以及如何取消对指针的引用之类的知识。并且,就此而言,知道并理解什么是指针。您必须知道什么是堆栈帧以及什么是指令指针。您必须了解正在使用的CPU体系结构的内存模型。对微型计算机的架构有很多隐含的理解(通常使用C#或Java进行编程的C语言中根本不存在,也没有必要。所有这些信息已被卸载给编译器(或VM)编程器。


“如何解决更多的硬件问题”仅在实际可行的环境中起作用。嵌入式市场是一个完美的反例(这是一个庞大的市场)。
鲍勃·萨默斯

Jeff和Joel的博客仅涉及业务系统,而不涉及嵌入式系统,因此可以合理地假设这是提出此问题的上下文。
罗伯特·巴特

1).net代码的运行速度与C代码一样快?您是否曾经编写过C程序?2)“让更多的硬件出问题”的心态是为什么我的1.3GHz双核2GB计算机几乎不能跟上Windows XP,而我的800MHz 512MB计算机却随最新版本的Ubuntu一起运行。
罗伯特·格兰伯

是的,我写过C。它没有人们想象的那么光荣。而且项目成本太高。这是一个简单的经济学案例。我在带有768MB RAM的奔腾Pro 180MHz上运行Win2k作为邮件和Web服务器已有多年。运行得很好。轶事证据毫无意义。
罗伯特·巴特

C不是“光荣的”,但它很快,我编写了足够的C和C#代码,以了解C在执行相同任务时几乎总是比C#快得多。对于某些任务,用高级语言进行开发需要更长的时间,但这全都在于为工作使用正确的工具,有时甚至是C。
罗伯特·加伯

1

自动和手动之间是有区别的,高级语言是抽象的,因此是自动化的。C / C ++是手动控制和处理的,即使错误检查代码有时也需要人工。

C和C ++也是已编译的语言,这意味着它们都不在任何地方运行,这些语言必须针对您使用的硬件进行微调,从而增加了额外的陷阱。尽管随着C / C ++编译器在所有平台上变得越来越普遍,这种情况现在已逐渐消失。您可以在平台之间进行交叉编译。仍然不是到处运行的情况,您基本上是在指示编译器A针对编译器B编译相同代码,不同体系结构。

底线C语言并不是要易于理解或推理,这也是为什么将其称为系统语言。他们出现在所有这些高级抽象废话之前。这也是为什么它们不用于前端Web编程的原因。他们只是不适合这项任务,他们的意思是解决常规语言工具无法解决的复杂问题。

这就是为什么您会得到疯狂的东西(例如微体系结构,驱动程序,量子物理学,AAA游戏,操作系统)之类的东西的原因,而C和C ++恰好适合这些东西。速度和数字紧缩是主要领域。



1

原因有很多,包括:

  • 它编译成汇编语言。
  • 它是静态类型的。
  • 没有垃圾收集。
  • 没有错误处理。
  • 许多编程语言添加了新功能。C语言的部分哲学是保持事物简单而不是添加更多功能。

为什么因为它不是对象对象,所以它应该更快?
sb27

A)面向对象的程序设计产生了对垃圾回收的需求,B)诸如面向对象的程序设计等重大功能增加了编译器的复杂性,
Sapphire_Brick

A)不。请参阅C ++或Rust。B)是的,但是它也为编译器提供了进行新优化的机会。
sb27

A)Rust具有编译时垃圾收集,而c ++具有类的垃圾收集,这就是为什么它具有析构函数的原因,B)优化的复杂度仍然是复杂度
Sapphire_Brick

A)这不是垃圾回收,即使您在程序集中制作程序,也必须执行内存管理。更多抽象允许优化器做出更好的假设。
sb27
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.