代码之家  ›  专栏  ›  技术社区  ›  John

C#vs C-性能差异大

  •  93
  • John  · 技术社区  · 16 年前

    #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));
            }
        }
    }
    

    正在使用mingw将C编译为Windows可执行文件。

    我一直认为C/C++更快,或者至少可以与C#net媲美。到底是什么导致C代码运行速度慢了30倍以上?

    编辑: 看起来C#优化器正在删除根,因为它没有被使用。 我将根分配更改为 root += 最后把总数打印出来。 我还使用cl.exe编译了C,并将/O2标志设置为“最大速度”。

    结果如下: C组为3.75秒 C组为2.61秒#

    13 回复  |  直到 3 年前
        1
  •  171
  •   Stack Overflow is garbage    16 年前

    您必须比较调试版本。我刚刚编译了你的C代码

    Time elapsed: 0.000000
    

    如果您不启用优化,那么您所做的任何基准测试都是毫无价值的。(如果您启用了优化,循环就会得到优化。因此,您的基准测试代码也有缺陷。您需要强制它运行循环,通常是通过总结结果或类似结果,并在最后打印出来)

    看起来您所衡量的基本上是“哪个编译器插入的调试开销最大”。答案是C,但这并不能告诉我们哪个程序最快。因为当您需要速度时,您可以启用优化。

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

    在C语言中,有些东西即使在一个幼稚的非优化编译器中也是有效的,还有一些东西严重依赖于编译器来优化一切。当然,C#或任何其他语言也是如此。

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

    一个好的C#编译器将产生高效的代码。一个坏的C编译器将生成慢代码。那生成C代码的C编译器呢,你可以通过C编译器来运行它?那会跑多快?语言没有速度。你的代码可以。

        2
  •  120
  •   Hans Passant    15 年前

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

        3
  •  63
  •   Community CDub    8 年前

    由于您从未使用过“root”,编译器可能已删除该调用以优化您的方法。

    您可以尝试将平方根值累加到累加器中,在方法末尾打印出来,然后查看发生了什么。

    编辑:参见 Jalf's answer 在下面

        4
  •  61
  •   Richard    14 年前

    我是C++和C语言开发人员。自从.NET框架的第一个beta版以来,我开发了C语言应用程序,并且我有20多年的C++开发经验。首先,C代码永远不会比C++应用程序快,但我不会经过长时间的讨论,关于托管代码,它是如何工作的,互操作层,内存管理内部,动态类型系统和垃圾收集器。然而,让我继续说,这里列出的基准都产生了错误的结果。

    我们首先要考虑的是Cy~(.NET Framework 4)的JIT编译器。现在JIT使用各种优化算法生成CPU的本地代码(它往往比VisualStudio附带的默认C++优化器更具攻击性)NET JIT编译器使用的指令集更能反映机器上的实际CPU,因此可以在机器代码中进行某些替换,以减少时钟周期,提高CPU管道缓存中的命中率,并产生进一步的超线程优化,如指令重新排序和改进与分支预测有关的。

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

    统计数据中还缺少计时中未包含的启动和清理时间。C应用程序往往比C++应用程序在启动和终止上花费更多的时间。这背后的原因很复杂,与.NET运行时代码验证例程和内存管理子系统有关。内存管理子系统在程序的开始(以及最终)执行大量工作,以优化内存分配和垃圾收集器。

    在测量C++和.NET IL的性能时,查看汇编代码以确保所有计算都是重要的。我发现,没有在C#中添加一些额外的代码,上面示例中的大多数代码实际上都是从二进制文件中删除的。这也是C++中使用一个更积极的优化器的原因,比如英特尔C++编译器中的一个。我上面提供的结果是100%正确的,并在组装级别得到验证。

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

        5
  •  10
  •   Neil N HLGEM    15 年前

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

        6
  •  7
  •   anon anon    16 年前

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

    root += Math.Sqrt(i);
    

    ans在C代码中类似,然后在循环外打印root的值。

        7
  •  6
  •   i_am_jorf    16 年前

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

    编辑:我认为编译器不能只优化for循环,因为它必须知道Math.Sqrt()没有任何副作用。

        8
  •  5
  •   Tom    16 年前

    无论时间差是多少,“经过的时间”都是无效的。只有当您能够保证两个程序在完全相同的条件下运行时,它才是有效的。

        9
  •  5
  •   Cecil Has a Name    16 年前

    我(根据您的代码)用C和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));
            }
        }
    }
    

    这些测试将数据写入数组(因此不应允许.NET运行时剔除sqrt op),尽管数组要小得多(不希望使用过多内存)。我在releaseconfig中编译了它们,并在控制台窗口中运行它们(而不是通过VS启动)。

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

        10
  •  5
  •   Mike Dunlavey    16 年前

        11
  •  2
  •   David M    16 年前

    这里可能存在问题的另一个因素是,C编译器编译为目标处理器系列的通用本机代码,而编译C#代码时生成的MSIL则被JIT编译为目标处理器,并完成任何可能的优化。因此,从C#生成的本机代码可能比C#快得多。

        12
  •  1
  •   Jack Ryan    16 年前

        13
  •  1
  •   Dana    16 年前

     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。但这仅仅是增加1亿个加法操作符的开销吗?