在收听StackOverflow播客时,jab不断出现"真正的程序员"用C语言编写,而C语言更快,因为它"靠近机器".将前一个断言留给另一个帖子,C的特殊之处在于它是否比其他语言更快?或者换一种方式:什么阻止其他语言能够编译成二进制文件,它运行速度和C一样快?
关于C没什么特别之处.这就是为什么它快速的原因之一.
较新的语言,支持垃圾收集,动态类型和其他设施,使程序员更容易编写程序.
问题是,存在额外的处理开销,这将降低应用程序的性能.C没有任何这些,这意味着没有开销,但这意味着程序员需要能够分配内存并释放它们以防止内存泄漏,并且必须处理变量的静态类型.
也就是说,许多语言和平台,例如Java(带有Java虚拟机)和.NET(带有公共语言运行时),多年来都有了改进的性能,例如即时编译,从而产生本机机器代码.字节码实现更高的性能.
C设计师已经做出了折衷.也就是说,他们决定将速度提高到安全之上.C不会
检查数组索引边界
检查未初始化的变量值
检查内存泄漏
检查空指针取消引用
当您索引到数组时,在Java中,它需要在虚拟机中进行一些方法调用,绑定检查和其他健全性检查.这是有效且绝对正常的,因为它增加了应有的安全性.但在C语言中,即使是非常琐碎的事情也不会安全.例如,C不需要memcpy来检查要复制的区域是否重叠.它不是设计为编写大型业务应用程序的语言.
但是这些设计决策并不是C语言中的错误.它们是设计的,因为它允许编译器和库编写者从计算机中获得所有性能.以下是C的精神C Rationale文档如何解释它:
C代码可以是不可移植的.虽然它努力为程序员提供编写真正可移植程序的机会,但委员会并不想强迫程序员编写程序,以排除使用C作为"高级汇编程序":编写特定于机器的能力代码是C的优势之一.
保持C的精神.委员会将保持C的传统精神作为一个主要目标.C的精神有许多方面,但其实质是C语言所依据的基本原则的社区情感.C精神的某些方面可以用短语来概括
相信程序员.
不要阻止程序员做需要做的事情.
保持语言小而简单.
只提供一种方法来进行操作.
即使不保证便携,也要快速.
最后一句谚语需要一点解释.高效代码生成的潜力是C最重要的优势之一.为了帮助确保看起来非常简单的操作不会发生代码爆炸,许多操作被定义为目标机器的硬件如何做而不是通过一般的抽象规则.在管理用于表达式的char对象的扩展的规则中可以看到这种意愿与机器一起工作的一个例子:char对象的值扩展到有符号或无符号量通常取决于哪个字节操作更多在目标机器上高效.
如果你用一个月的时间在C中构建一个运行时间为0.05秒的东西,并且我花了一天时间在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程序员必须使用每几行代码进行这些类型的低级决策.它增加了手工制作的手动优化可执行文件.
我没有看到它,所以我会说: C往往更快,因为几乎所有其他东西都是用C语言编写的.
Java是基于C构建的,Python是基于C(或Java,或.NET等)构建的,Perl是等等.操作系统是用C语言编写的,虚拟机是用C语言编写的,编译器是用C语言编写的,解释器是用C语言编写的.有些东西仍然用汇编语言编写,后者往往更快.越来越多的东西被写在其他东西上,这本身就是用C语写的.
您使用其他语言(而不是Assembly)编写的每个语句通常在C下面作为几个语句实现,这些语句被编译为本机机器代码.由于为了获得比C更高的抽象级别而倾向于存在其他语言,因此C中所需的那些额外语句往往侧重于增加安全性,增加复杂性和提供错误处理.这些往往是好事,但它们有成本,其名称是速度和大小.
就个人而言,我已经用几十种语言编写了大部分可用频谱,我个人已经找到了你暗示的魔力:
我怎么能吃蛋糕呢?我怎样才能用我最喜欢的语言进行高级抽象,然后深入了解C的细节以获得速度?
经过几年的研究,我的答案是Python(在C上).你可能想看一看.顺便说一句,您也可以从Python下载到Assembly(在特殊库的帮助下).
另一方面,坏代码可以用任何语言编写.因此,C(或汇编)代码不会自动更快.同样,一些优化技巧可以使部分高级语言代码接近原始C的性能级别.但是,对于大多数应用程序,您的程序大部分时间都在等待人员或硬件,因此差异无关紧要.
请享用.
那里有很多问题 - 主要是我没有资格回答的问题.但对于最后一个:
是什么阻止其他语言能够编译成与C一样快的每一位运行的二进制文件?
总之,抽象.
C只是机器语言的一个或两个抽象层次.Java和.Net语言至少有3个抽象级别,远离汇编程序.我不确定Python和Ruby.
通常情况下,程序员玩具越多(复杂的数据类型等),您就越远离机器语言,必须完成更多的翻译工作.
我不在这里,但这是基本的要点.
更新 -------这篇文章有一些很好的评论,有更多细节.
由于C的成本模型是透明的,因此C的速度并不快.如果C程序很慢,那么它显然很慢:通过执行大量语句.与C中的操作成本相比,对对象(尤其是反射)或字符串的高级操作可能会产生不明显的成本.
通常编译为与C一样快的二进制文件的两种语言是标准ML(使用MLton编译器)和Objective Caml.如果你查看基准测试游戏,你会发现对于某些基准测试,比如二叉树,OCaml版本比C更快.(我没有找到任何MLton条目.)但是不要太认真地对待枪战; 正如它所说的那样,游戏结果通常反映了人们在调整代码方面投入了多少精力.
C并不总是更快.
C比现代Fortran慢.
对于某些事情,C通常比Java慢.(特别是在JIT编译器完成了代码之后)
C允许指针别名发生,这意味着一些好的优化是不可能的.特别是当您有多个执行单元时,这会导致数据获取停顿.噢.
假设指针算法确实能够在一些CPU系列上引起缓慢的性能(特别是PIC!)它曾经在分段x86上吮吸大的.
基本上,当你得到一个向量单元或并行编译器时,C会发臭,现代Fortran运行得更快.
C程序员的技巧就像thunking(动态修改可执行文件)导致CPU预取停顿.
你得到漂移?
我们的好朋友x86执行一套指令集,这些指令集与实际的CPU架构几乎没有关系.影子寄存器,加载存储优化器,都在CPU中.所以C接近虚拟金属.真正的金属,英特尔不会让你看到.(历史上VLIW CPU有点像一个半身像,所以也许没有那么糟糕.)
如果你在高性能DSP(可能是TI DSP?)上用C编程,编译器必须做一些棘手的工作来在多个并行执行单元中展开C. 所以在这种情况下C不接近金属,但它接近编译器,这将完成整个程序.奇怪的.
最后,一些CPU(www.ajile.com)在硬件中运行Java字节码.C将在PITA上使用该CPU.
是什么阻止其他语言能够编译成与C一样快的每一位运行的二进制文件?
没有.Java或.NET等现代语言更多地依赖于程序员的生产力而不是性能.硬件现在很便宜.编译到中间表示也提供了许多奖励,例如安全性,可移植性等..NET CLR可以利用不同的硬件 - 例如,您不需要手动优化/重新编译程序以使用SSE指令集.
主要因素是它是一种静态类型的语言,并编译为机器代码.此外,由于它是一种低级语言,它通常不会做任何你不告诉它的事情.
这些是我想到的其他一些因素.
变量不会自动初始化
没有边界检查数组
未经检查的指针操作
没有整数溢出检查
静态类型的变量
函数调用是静态的(除非你使用函数指针)
编译器编写者有很多时间来改进优化代码.此外,人们在C中编程以获得最佳性能,因此优化代码的压力很大.
语言规范的一部分是实现定义的,因此编译器可以以最佳方式自由地执行操作
大多数静态类型的语言可以像C一样快速或快速地编译,特别是如果他们可以做出假设C不能因为指针别名等等.
我猜你忘了汇编语言也是一种语言:)
但严重的是,只有当程序员知道他在做什么时,C程序才会更快.您可以轻松编写一个C程序,该程序运行速度比使用其他语言执行相同工作的程序慢.
C更快的原因是因为它是以这种方式设计的.它可以让你做很多"低级"的东西,帮助编译器优化代码.或者,我们应该说,程序员负责优化代码.但它通常非常棘手且容易出错.
与其他语言一样,其他语言更多地关注程序员的工作效率.人们普遍认为程序员的时间比机器时间要贵得多(即使在过去).因此,最大限度地减少程序员花在编写和调试程序上的时间而不是程序的运行时间是很有意义的.要做到这一点,你将牺牲一些你可以做的事情来使程序更快,因为许多事情是自动化的.
C++的平均速度更快(因为它最初是C的超级集合).但是对于特定的基准测试,通常会有另一种语言更快.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/
fannjuch-redux
在Scala中速度最快
n-body
并fasta
分别在阿达更快.
spectral-norm
在Fortran中最快.
reverse-complement
,mandelbrot
并且pidigits
是最快的ATS.
regex-dna
是最快的JavaScript.
chameneou-redux
最快的是Java 7.
thread-ring
在Haskell最快.
其余的基准测试在C或C++中最快.
我知道很多人都说了很长的话,但是:
C更快,因为它为您做的更少。
这些答案中的许多答案给出了为什么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源代码访问,并进入上面的简单one-linef.你会遇到的一个有趣的小宝石是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
结构,什么是传递到CreateWindowEx
从检索Control
十倍类之前它存储在WNDCLASSEX
结构上传递RegisterClassEx
和CreateWindowEx
.
总而言之,执行此基本任务所执行的指令数量在C#中比在C中多出2-3个数量级.部分原因是由于使用了功能丰富的库,这必然是一般化的,而不是我们的简单C代码完全符合我们的需求,仅此而已.但其中一部分原因在于.NET框架的模块化,面向对象的特性使得自身经常会通过程序方法避免重复执行.
我不是试图选择C#或.NET框架.我也不是说模块化,泛化,库/语言特性,OOP等都是坏事.我曾经在C中进行大部分开发,后来在C++中进行,最近在C#中进行.同样,在C之前,我主要使用汇编.随着语言的每一步"更高",我会在更短的时间内编写更好,更易维护,更强大的程序.但是,它们的执行速度往往会慢一些.
我认为没有人提到过C编译器比其他任何编译器都付出更多努力的事实,可能除了Java之外.
由于许多原因,C极其优化 - 比几乎任何其他语言都要多.因此,如果将相同的努力量投入到其他语言编译器中,C可能仍会名列前茅.
我认为至少有一种候选语言可以比C更好地优化,因此我们可以看到生成更快二进制文件的实现.我正在考虑数字火星D,因为创建者注意构建一种可能比C更好地优化的语言.可能还有其他语言具有这种可能性.但是我无法想象任何语言都会让编译器比最好的C编译器快几个百分点.我想爱错.
我认为真正的"低悬的果实"将是用于人类优化的语言.熟练的程序员可以使任何语言更快 - 但有时你必须做荒谬的事情或使用不自然的结构来实现这一点.虽然它总是需要付出努力,但是一个好的语言应该能够产生相对快速的代码,而不必过于专注于程序的编写方式.
同样重要的是(至少对我而言)最坏情况的代码往往很快.网上有许多"证明",Java比C更快或更快,但这是基于樱桃挑选示例.我不是C的忠实粉丝,但我知道我用C写的任何东西都会运行良好.使用Java,它"可能"在15%的速度内运行,通常在25%以内,但在某些情况下可能会更糟.任何情况下,它的速度一样快或在几个百分之内,通常是由于大部分时间花在库代码上,无论如何都要进行大量优化.
在大多数情况下,每个C指令对应于非常少的汇编指令.您实际上是在编写更高级别的机器代码,因此您几乎可以控制处理器所做的一切.许多其他编译语言,如C++,有很多简单易懂的指令,可以转化为比你想象的更多的代码(虚函数,复制构造函数等).而像Java或Ruby这样的解释语言有另外一层您从未见过的说明 - 虚拟机或解释器.
这实际上是一种长期存在的谎言.虽然C程序经常更快,但情况并非总是如此,特别是如果C程序员不是很擅长的话.
人们倾向于忘记的一个很大的漏洞是程序必须阻止某种IO,例如任何GUI程序中的用户输入.在这些情况下,使用何种语言并不重要,因为您受到数据进入速率的限制,而不是处理数据的速度.在这种情况下,如果您使用C,Java,C#甚至Perl,这并不重要; 你不能比数据进来更快.
另一个主要问题是使用垃圾收集而不使用正确的指针允许虚拟机进行许多其他语言不可用的优化.例如,JVM能够在堆上移动对象以对其进行碎片整理.这使得将来的分配更快,因为可以简单地使用下一个索引而不是在表中查找它.现代JVM也不必实际释放内存; 相反,他们只是移动活动对象,当他们GC和死对象的花费的内存基本上是免费恢复时.
这也引出了一个关于C的有趣观点,在C++中更是如此.有一种设计理念是"如果你不需要它,你就不需要付钱".问题是,如果你真的想要它,你最终会为此付出代价.例如,Java中的vtable实现往往比C++实现好很多,因此虚函数调用要快得多.另一方面,你别无选择,只能在Java中使用虚拟功能,但它们仍然需要花费一些成本,但在使用大量虚拟功能的程序中,降低的成本会增加.