C#vs C – 巨大的性能差异

我发现在C和C#中类似的代码之间有巨大的性能差异。

C代码是:

#include <stdio.h> #include <time.h> #include <math.h> main() { int i; double root; clock_t start = clock(); for (i = 0 ; i <= 100000000; i++){ root = sqrt(i); } printf("Time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); } 

而C#(控制台应用程序)是:

 using System; using System.Collections.Generic; using System.Text; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { DateTime startTime = DateTime.Now; double root; for (int i = 0; i <= 100000000; i++) { root = Math.Sqrt(i); } TimeSpan runTime = DateTime.Now - startTime; Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds/1000)); } } } 

用上面的代码,C#在0.328125秒(发布版本)完成,C需要11.14秒运行。

c正在使用mingw编译成windows可执行文件。

我一直认为C / C ++速度更快,或者至less与C#.net相当。 究竟是什么导致C运行速度慢了30倍?

编辑:它似乎确实是C#优化器正在删除它的根没有被使用。 我将根分配更改为root + =并在最后打印出总数。 我也编译C使用cl.exe与/ O2标志设置为最大速度。

现在的结果是:3.75秒C 2.61秒C#

C仍然需要更长时间,但这是可以接受的

由于您从不使用“root”,因此编译器可能会删除调用来优化您的方法。

您可以尝试将平方根值累加到累加器中,在方法结束时将其打印出来,看看发生了什么。

编辑:请参阅下面的Jalf的答案

您必须比较debugging版本。 我刚刚编译你的C代码,并得到

 Time elapsed: 0.000000 

如果你不启用优化,你所做的任何基准testing都是毫无价值的。 (如果你确实启用了优化,循环得到了优化,所以你的基准代码也是有缺陷的,你需要迫使它运行循环,通常是总结结果或类似的结果,并在最后打印出来)

看来你所测量的基本上是“哪个编译器插入最多的debugging开销”。 原来答案是C.但是这并不能告诉我们哪个程序是最快的。 因为当你想要速度,你启用优化。

顺便说一句,如果放弃任何语言比其他语言“更快”的概念,那么从长远来看,你将会为自己省去很多麻烦。 C#没有比英语更快的速度了。

在C语言中有一些东西甚至在一个天真的非优化编译器中也是有效的,还有一些在很大程度上依赖编译器来优化所有东西。 当然,C#或任何其他语言也是如此。

执行速度取决于:

  • 您正在运行的平台(操作系统,硬件,系统上运行的其他软件)
  • 编译器
  • 你的源代码

一个好的C#编译器将会产生高效的代码。 糟糕的C编译器会生成慢速代码。 怎么样一个C编译器生成C#代码,然后你可以通过C#编译器运行? 运行速度有多快? 语言没有速度。 你的代码是。

我会保持简短,它已经被标记为回答。 C#具有定义良好的浮点模型的巨大优势。 恰好与x86和x64处理器上的FPU和SSE指令集的本机操作模式相匹配。 那里没有巧合。 JITter将Math.Sqrt()编译为一些内联指令。

本机C / C ++具有多年的向后兼容性。 / fp:precise,/ fp:fast和/ fp:strict编译选项是最明显的。 因此,它必须调用实现sqrt()的CRT函数并检查选定的浮点选项来调整结果。 这很慢。

我是C ++和C#开发人员。 从.NET框架的第一个testing版开始,我开发了C#应用程序,并且在开发C ++应用程序方面有20多年的经验。 首先,C#代码永远不会比C ++应用程序更快,但是我不会经历冗长的讨论有关托pipe代码,它是如何工作的,内部操作层,内存pipe理内部,dynamictypes系统和垃圾收集器。 不过,让我继续说,这里列出的基准都产生了不正确的结果。

让我来解释一下:我们需要考虑的第一件事情就是用于C#(.NET Framework 4)的JIT编译器。 现在,JIT使用各种优化algorithm(比Visual Studio自带的默认C ++优化程序更积极)为CPU生成本地代码,而.NET JIT编译器使用的指令集更贴近实际CPU因此在机器上可以进行机器代码中的某些replace以减less时钟周期并提高CPUpipe线高速caching中的命中率,并产生进一步的超线程优化,例如指令重新sorting和与分支预测相关的改进。

这意味着,除非您使用正确的参数编译您的C ++应用程序(而不是DEBUG编译),否则您的C ++应用程序可能比相应的基于C#或.NET的应用程序执行得更慢。 在C ++应用程序中指定项目属性时,确保启用“完全优化”和“优先快速代码”。 如果你有一个64位的机器,你必须指定生成x64作为目标平台,否则你的代码将通过转换子层(WOW64)来执行,这将大大降低性能。

一旦在编译器中执行了正确的优化,C ++应用程序就会得到0.72秒,而C#应用程序得到1.16秒(在发行版本中)。 由于C#应用程序是非常基本的,并且在堆栈中而不是在堆上分配循环中使用的内存,所以它实际上比涉及对象,大量计算和大型数据集的实际应用要好得多。 所以提供的数字是偏向C#和.NET框架的乐观数字。 即使有这种偏见,C ++应用程序的完成时间也只是等同的C#应用​​程序的一半以上。 请记住,我使用的Microsoft C ++编译器没有正确的pipe道和超线程优化(使用WinDBG查看汇编指令)。

现在,如果我们使用Intel编译器(在AMD / Intel处理器上生成高性能应用程序,这是一个行业秘密),相同的代码对于C ++可执行文件执行时间为0.54秒,使用Microsoft Visual Studio 2010执行时间为0.72秒所以最终的结果是C ++为0.54秒,C#为1.16秒。 所以.NET JIT编译器生成的代码比C ++可执行文件长214%。 在.54秒花费的大部分时间是从系统获取时间,而不是在循环本身!

统计中还缺less的是启动和清理时间,这些时间并不包括在内。 与C ++应用程序相比,C#应用程序往往在启动和终止上花费更多的时间。 这背后的原因是复杂的,并且与.NET运行时代码validation例程和内存pipe理子系统有关,它在程序开始(并因此结束)执行大量工作来优化内存分配和垃圾集电极。

在测量C ++和.NET IL的性能时,重要的是查看汇编代码以确保所有的计算都在那里。 我发现,如果不在C#中添加一些额外的代码,上面示例中的大部分代码实际上已经从二进制文件中删除了。 当您使用更积极的优化器(如英特尔C ++编译器附带的优化器)时,C ++也是如此。 上面提供的结果是100%正确的,并在组装级别validation。

很多新手都在听不懂微软的营销宣传而不了解技术,并且声称C#比C ++更快,这是互联网上很多论坛的主要问题。 从理论上讲,C#比C ++更快,因为JIT编译器可以优化CPU的代码。 这个理论的问题是在.NET框架中存在很多pipe道,会降低性能; 在C ++应用程序中不存在的pipe道。 此外,有经验的开发人员将知道用于给定平台的正确编译器,并在编译应用程序时使用适当的标志。 在Linux或开源平台上,这不是问题,因为您可以分发源代码并创build安装脚本,使用适当的优化来编译代码。 在Windows或封闭源代码平台上,您将不得不分发多个可执行文件,每个都有特定的优化。 将部署的Windows二进制文件基于msi安装程序检测到的CPU(使用自定义操作)。

我的第一个猜测是编译器优化,因为你永远不会使用root。 您只需分配它,然后一遍又一遍地覆盖它。

编辑:该死的,击败了9秒!

要查看循环是否正在优化,请尝试将代码更改为

 root += Math.Sqrt(i); 

同样在C代码中,然后打印循环外部的root值。

也许c#编译器注意到你没有在任何地方使用root,所以它只是跳过整个for循环。 🙂

这可能不是这种情况,但我怀疑是什么原因,这是编译器实现依赖。 尝试使用Microsoft编译器(cl.exe,作为win32 sdk的一部分提供),使用优化和发布模式编译C程序。 我敢打赌你会看到相对于其他编译器的性能改进。

编辑:我不认为编译器可以只是优化了for循环,因为它将不得不知道Math.Sqrt()没有任何副作用。

无论时间差异。 可能是,那“经过的时间”是无效的。 如果你能保证两个程序在完全相同的条件下运行,那将只是一个有效的方法。

也许你应该尝试一个胜利。 相当于$ / usr / bin / time my_cprog; / usr / bin / time my_csprog

我放在一起(根据你的代码)在C和C#中两个更类似的testing。 这两个使用模数运算符编写一个较小的数组进行索引(它增加了一些开销,但是,嘿,我们试图比较性能[在一个粗糙的水平])。

C代码:

 #include <stdlib.h> #include <stdio.h> #include <time.h> #include <math.h> void main() { int count = (int)1e8; int subcount = 1000; double* roots = (double*)malloc(sizeof(double) * subcount); clock_t start = clock(); for (int i = 0 ; i < count; i++) { roots[i % subcount] = sqrt((double)i); } clock_t end = clock(); double length = ((double)end - start) / CLOCKS_PER_SEC; printf("Time elapsed: %f\n", length); } 

在C#中:

 using System; namespace CsPerfTest { class Program { static void Main(string[] args) { int count = (int)1e8; int subcount = 1000; double[] roots = new double[subcount]; DateTime startTime = DateTime.Now; for (int i = 0; i < count; i++) { roots[i % subcount] = Math.Sqrt(i); } TimeSpan runTime = DateTime.Now - startTime; Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds / 1000)); } } } 

这些testing将数据写入数组(所以.NET运行时不应该被允许剔除sqrt操作),尽pipe数组要小得多(不想使用过多的内存)。 我编译这些在释放configuration,并从控制台窗口内运行(而不是从VS开始)。

在我的电脑上,C#程序在6.2到6.9秒之间变化,而C版本在6.9到7.1之间变化。

如果您只是在程序集级别单步执行代码,包括逐步执行平方根例程,则可能会得到问题的答案。

不需要受过教育的猜测。

另一个可能成为问题的因素是,C编译器编译为您的目标处理器系列的通用本机代码,而编译C#代码时生成的MSIL则编译为针对您已完成的确切处理器可能的优化。 所以从C#生成的本地代码可能比C更快

在我看来,这与语言本身无关,而是与平方根函数的不同实现有关。

其实,球员,循环没有被优化。 我编译了John的代码并检查了生成的.exe。 循环的内容如下:

  IL_0005: stloc.0 IL_0006: ldc.i4.0 IL_0007: stloc.1 IL_0008: br.s IL_0016 IL_000a: ldloc.1 IL_000b: conv.r8 IL_000c: call float64 [mscorlib]System.Math::Sqrt(float64) IL_0011: pop IL_0012: ldloc.1 IL_0013: ldc.i4.1 IL_0014: add IL_0015: stloc.1 IL_0016: ldloc.1 IL_0017: ldc.i4 0x5f5e100 IL_001c: ble.s IL_000a 

除非运行时足够聪明才能实现循环,什么也不做,并跳过它?

编辑:改变C#是:

  static void Main(string[] args) { DateTime startTime = DateTime.Now; double root = 0.0; for (int i = 0; i <= 100000000; i++) { root += Math.Sqrt(i); } System.Console.WriteLine(root); TimeSpan runTime = DateTime.Now - startTime; Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds / 1000)); } 

结果(在我的机器上)的时间从0.047到2.17。 但这仅仅是增加了一亿个运营商的开销?