12

确保在 IDE 之外运行。那是关键。

-edit- 我喜欢 SLaks 的评论。“这些答案中的错误信息数量惊人。” :D

冷静下来,伙计们。几乎所有人都错了。我确实进行了优化。 事实证明,我所做的任何优化都不够好。 我使用 gettimeofday 在 GCC 中运行代码(我将在下面粘贴代码)并使用g++ -O2 file.cpp并获得比 C# 稍快的结果。 也许 MS 没有在这种特定情况下创建所需的优化,但在下载和安装 mingw 后,我经过测试发现速度几乎相同。 正义似乎是对的。我本可以发誓我在我的电脑上使用时钟并用它来计数,发现它速度较慢但问题解决了。在 MS 编译器中,C++ 的速度几乎没有慢两倍。

当我的朋友告诉我这件事时,我简直不敢相信。所以我拿了他的代码并在上面放了一些计时器。

我使用 C#而不是Boo 。我在 C# 中不断得到更快的结果。为什么?无论我使用什么数字,.NET 版本的时间几乎都是一半。

C++ 版本(坏版本):

#include <iostream>
#include <stdio.h>
#include <intrin.h>
#include <windows.h>
using namespace std;

int fib(int n)
{
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

int main()
{
    __int64 time = 0xFFFFFFFF;
    while (1)
    {
        int n;
        //cin >> n;
        n = 41;
        if (n < 0) break;
__int64 start = __rdtsc();
        int res = fib(n);
__int64 end = __rdtsc();
        cout << res << endl;
        cout << (float)(end-start)/1000000<<endl;
        break;
    }

    return 0;
}

C++ 版本(更好的版本):

#include <iostream>
#include <stdio.h>
#include <intrin.h>
#include <windows.h>
using namespace std;

int fib(int n)
{
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

int main()
{
    __int64 time = 0xFFFFFFFF;
    while (1)
    {
        int n;
        //cin >> n;
        n = 41;
        if (n < 0) break;
        LARGE_INTEGER start, end, delta, freq;
        ::QueryPerformanceFrequency( &freq );
        ::QueryPerformanceCounter( &start );
        int res = fib(n);
        ::QueryPerformanceCounter( &end );
        delta.QuadPart = end.QuadPart - start.QuadPart;
        cout << res << endl;
        cout << ( delta.QuadPart * 1000 ) / freq.QuadPart <<endl;
break;
    }

    return 0;
}

C#版本:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Threading;
using System.IO;

using System.Diagnostics;

namespace fibCSTest
{
    class Program
    {
         static int fib(int n)
         {
            if (n < 2)return n;
            return fib(n - 1) + fib(n - 2);
         }

         static void Main(string[] args)
         {
             //var sw = new Stopwatch();
             //var timer = new PAB.HiPerfTimer();
             var timer = new Stopwatch();
             while (true)
             {
                 int n;
                 //cin >> n;
                 n = 41;
                 if (n < 0) break;
                 timer.Start();
                 int res = fib(n);
                 timer.Stop();
                 Console.WriteLine(res);
                 Console.WriteLine(timer.ElapsedMilliseconds);
                 break;
             }
         }
    }
}

海合会版本:

#include <iostream>
#include <stdio.h>
#include <sys/time.h>
using namespace std;

int fib(int n)
{
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

int main()
{
    timeval start, end;
    while (1)
    {
        int n;
        //cin >> n;
        n = 41;
        if (n < 0) break;
        gettimeofday(&start, 0);
        int res = fib(n);
        gettimeofday(&end, 0);
        int sec = end.tv_sec - start.tv_sec;
        int usec = end.tv_usec - start.tv_usec;
        cout << res << endl;
        cout << sec << " " << usec <<endl;
        break;
    }

    return 0;
}
4

13 回答 13

16

编辑:TL/DR 版本:CLR JIT 将内联一级递归,MSVC 8 SP1 不会没有#pragma inline_recursion(on). 您应该在调试器之外运行 C# 版本以获得完全优化的 JIT。

在使用“高性能”电源设置(~1600 ms 对 ~3800 ms)运行 Vista 的 Core2 Duo 笔记本电脑上使用 VS 2008 SP1 使用 C# 与 C++ 时,我得到了与 acidzombie24 相似的结果。查看优化的 JIT'd C# 代码有点棘手,但对于 x86,它归结为:

00000000 55               push        ebp  
00000001 8B EC            mov         ebp,esp 
00000003 57               push        edi  
00000004 56               push        esi  
00000005 53               push        ebx  
00000006 8B F1            mov         esi,ecx 
00000008 83 FE 02         cmp         esi,2 
0000000b 7D 07            jge         00000014 
0000000d 8B C6            mov         eax,esi 
0000000f 5B               pop         ebx  
00000010 5E               pop         esi  
00000011 5F               pop         edi  
00000012 5D               pop         ebp  
00000013 C3               ret              
            return fib(n - 1) + fib(n - 2);
00000014 8D 7E FF         lea         edi,[esi-1] 
00000017 83 FF 02         cmp         edi,2 
0000001a 7D 04            jge         00000020 
0000001c 8B DF            mov         ebx,edi 
0000001e EB 19            jmp         00000039 
00000020 8D 4F FF         lea         ecx,[edi-1] 
00000023 FF 15 F8 2F 12 00 call        dword ptr ds:[00122FF8h] 
00000029 8B D8            mov         ebx,eax 
0000002b 4F               dec         edi  
0000002c 4F               dec         edi  
0000002d 8B CF            mov         ecx,edi 
0000002f FF 15 F8 2F 12 00 call        dword ptr ds:[00122FF8h] 
00000035 03 C3            add         eax,ebx 
00000037 8B D8            mov         ebx,eax 
00000039 4E               dec         esi  
0000003a 4E               dec         esi  
0000003b 83 FE 02         cmp         esi,2 
0000003e 7D 04            jge         00000044 
00000040 8B D6            mov         edx,esi 
00000042 EB 19            jmp         0000005D 
00000044 8D 4E FF         lea         ecx,[esi-1] 
00000047 FF 15 F8 2F 12 00 call        dword ptr ds:[00122FF8h] 
0000004d 8B F8            mov         edi,eax 
0000004f 4E               dec         esi  
00000050 4E               dec         esi  
00000051 8B CE            mov         ecx,esi 
00000053 FF 15 F8 2F 12 00 call        dword ptr ds:[00122FF8h] 
00000059 03 C7            add         eax,edi 
0000005b 8B D0            mov         edx,eax 
0000005d 03 DA            add         ebx,edx 
0000005f 8B C3            mov         eax,ebx 
00000061 5B               pop         ebx  
00000062 5E               pop         esi  
00000063 5F               pop         edi  
00000064 5D               pop         ebp  
00000065 C3               ret  

与 C++ 生成的代码(/Ox /Ob2 /Oi /Ot /Oy /GL /Gr)相比:

int fib(int n)
{ 
00B31000 56               push        esi  
00B31001 8B F1            mov         esi,ecx 
    if (n < 2) return n; 
00B31003 83 FE 02         cmp         esi,2 
00B31006 7D 04            jge         fib+0Ch (0B3100Ch) 
00B31008 8B C6            mov         eax,esi 
00B3100A 5E               pop         esi  
00B3100B C3               ret              
00B3100C 57               push        edi  
    return fib(n - 1) + fib(n - 2); 
00B3100D 8D 4E FE         lea         ecx,[esi-2] 
00B31010 E8 EB FF FF FF   call        fib (0B31000h) 
00B31015 8D 4E FF         lea         ecx,[esi-1] 
00B31018 8B F8            mov         edi,eax 
00B3101A E8 E1 FF FF FF   call        fib (0B31000h) 
00B3101F 03 C7            add         eax,edi 
00B31021 5F               pop         edi  
00B31022 5E               pop         esi  
} 
00B31023 C3               ret              

C# 版本基本上是内联fib(n-1)fib(n-2). 对于这么call重的函数,减少函数调用的次数是提速的关键。替换fib为以下内容:

int fib(int n);

int fib2(int n) 
{ 
    if (n < 2) return n; 
    return fib(n - 1) + fib(n - 2); 
} 

int fib(int n)
{ 
    if (n < 2) return n; 
    return fib2(n - 1) + fib2(n - 2); 
} 

将其降至约 1900 毫秒。顺便说一句,如果我使用#pragma inline_recursion(on)我会得到与原始fib. 再展开一层:

int fib(int n);

int fib3(int n) 
{ 
    if (n < 2) return n; 
    return fib(n - 1) + fib(n - 2); 
} 

int fib2(int n) 
{ 
    if (n < 2) return n; 
    return fib3(n - 1) + fib3(n - 2); 
} 

int fib(int n)
{ 
    if (n < 2) return n; 
    return fib2(n - 1) + fib2(n - 2); 
} 

将其降至约 1380 毫秒。除此之外,它逐渐减少。

因此,我的机器的 CLR JIT 似乎会将递归调用内联一级,而 C++ 编译器默认情况下不会这样做。

如果只有所有性能关键代码都像fib

于 2010-02-18T07:21:14.693 回答
8

编辑:虽然最初的 C++ 计时是错误的(将周期与毫秒进行比较),但更好的计时确实表明 C# 使用 vanilla 编译器设置更快。

好的,足够的随机猜测,是时候进行一些科学了。在使用现有的 C++ 代码得到奇怪的结果后,我尝试运行:

int fib(int n)
{
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

int main()
{
    __int64 time = 0xFFFFFFFF;
    while (1)
    {
        int n;
        //cin >> n;
        n = 41;
        if (n < 0) break;
        LARGE_INTEGER start, end, delta, freq;
        ::QueryPerformanceFrequency( &freq );
        ::QueryPerformanceCounter( &start );
        int res = fib(n);
        ::QueryPerformanceCounter( &end );
        delta.QuadPart = end.QuadPart - start.QuadPart;
        cout << res << endl;
        cout << ( delta.QuadPart * 1000 ) / freq.QuadPart <<endl;
break;
    }

    return 0;
}

编辑:

MSN 指出你应该在调试器之外计时 C#,所以我重新运行了所有内容:

最佳结果(VC2008,从命令行运行发布版本,未启用特殊选项)

  • C++ 原始代码 - 10239
  • C++ QPF - 3427
  • C# - 2166(在调试器中为 4700)。

原始 C++ 代码(带有rdtsc)没有返回毫秒,只是报告的时钟周期的一个因素,因此直接与StopWatch()结果进行比较是无效的。原来的时序代码是错误的。

注意StopWatch()使用 QueryPerformance* 调用:http: //msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

所以在这种情况下,C++ 比 C# 快。 这取决于您的编译器设置 - 请参阅 MSN 的答案。

于 2010-02-18T03:22:35.180 回答
4

不理解垃圾收集或控制台缓冲的答案。

可能是您在 C++ 中的计时器机制存在固有缺陷。

根据http://en.wikipedia.org/wiki/Rdtsc,您可能会得到错误的基准测试结果。

引:

虽然这使时间保持更加一致,但它可能会扭曲基准,其中在操作系统将处理器切换到更高速率之前,一定量的启动时间会以较低的时钟速率花费。这会使事情看起来比通常需要更多的处理器周期。

于 2010-02-18T02:35:14.740 回答
4

我认为问题在于您在 C++ 中的计时代码。

来自 MS 文档__rdtsc

生成 rdtsc 指令,该指令返回处理器时间戳。处理器时间戳记录自上次复位以来的时钟周期数。

也许试试GetTickCount()

于 2010-02-18T02:38:13.130 回答
3

并不是说这就是问题所在,但您可能想阅读如何:使用高分辨率计时器

另请参阅... http://en.wikipedia.org/wiki/Comparison_of_Java_and_C%2B%2B#Performance

几项主要针对数值基准的研究认为,在某些情况下,Java 可能比 C++ 更快,原因有很多:[8][9] 指针使优化变得困难,因为它们可能指向任意数据,尽管许多 C++ 编译器提供 C99关键字限制纠正了这个问题。[10] 与无限制地使用 malloc/new 的标准实现进行内存分配的 C++ 实现相比,Java 垃圾收集的实现可能具有更好的缓存一致性,因为它的分配通常是按顺序进行的。* 运行时编译可以潜在地使用运行时可用的附加信息来更有效地优化代码,例如知道代码将在哪个处理器上执行。

它是关于 Java 的,但开始解决 C 运行时和 JITed 运行时之间的性能问题。

于 2010-02-18T02:37:38.977 回答
2

也许 C# 能够在递归调用中展开堆栈?我认为这也减少了计算次数。

于 2010-02-18T02:47:09.920 回答
1

比较语言时要记住的一件重要事情是,如果您进行简单的逐行翻译,您并不是在比较苹果和苹果。

在一种语言中有意义的东西在另一种语言中可能会产生可怕的副作用。要真正比较性能特征,您需要 C# 版本和 C++,而这些版本的代码可能非常不同。例如,在 C# 中,我什至不会使用相同的函数签名。我会选择更像这样的东西:

IEnumerable<int> Fibonacci()
{
   int n1 = 0;
   int n2 = 1;

   yield return 1;
   while (true)
   {
      int n = n1 + n2;
      n1 = n2;
      n2 = n;
      yield return n;
   }
}

然后像这样包装:

public static int fib(int n)
{
    return Fibonacci().Skip(n).First();
}

这会做得更好,因为它从下到上利用上一个术语的计算来帮助构建下一个,而不是两组单独的递归调用。

如果你真的想在 C++ 中表现出色,你可以使用元编程让编译器预先计算你的结果,如下所示:

template<int N> struct fibonacci
{
    static const int value = fibonacci<N - 1>::value + fibonacci<N - 2>::value;
};

template<> struct fibonacci<1>
{
    static const int value = 1;
};

template<> struct fibonacci<0>
{
    static const int value = 0;
};
于 2010-02-18T17:28:21.960 回答
0

可能是这些方法在运行测试之前在运行时预先设置了......或者当 C++ 的代码cout被缓冲时,控制台是 API 的包装器,用于输出到控制台......我猜......

希望这会有所帮助,最好的问候,汤姆。

于 2010-02-18T02:27:12.707 回答
0

您在 c# 代码中调用将被内联的静态函数,而在 c++ 中您使用非静态函数。我有大约 1.4 秒的 c++ 时间。使用 g++ -O3 你可以有 1.21 秒。

您无法将 c# 与 c++ 与翻译错误的代码进行比较

于 2010-07-21T19:02:05.273 回答
-2

如果该代码确实是执行时间的 1/2,那么一些可能的原因是:

  • 如果在上述代码中的任何地方发生这种情况,垃圾收集会加速 C# 代码相对于 C++ 代码的执行。
  • 写入控制台的 C# 可能会被缓冲(C++ 可能不会,或者它可能效率不高)
于 2010-02-18T02:23:02.037 回答
-2

我知道.NET 编译器有英特尔优化。

于 2010-02-18T02:47:20.420 回答
-2

推测1

垃圾收集程序可能会起作用。

在 C++ 版本中,所有内存管理都将在程序运行时内联发生,这将计入最后一次。

在 .NET 中,公共语言运行时 (CLR) 的垃圾收集器 (GC) 是不同线程上的单独进程,并且通常在程序完成后清理程序。因此,您的程序将完成,时间将在内存释放之前打印出来。特别是对于通常在完成之前根本不会被清理的小程序。

这一切都取决于垃圾收集实现的细节(以及它是否以与堆相同的方式优化堆栈),但我认为这在速度提升中起着部分作用。如果 C++ 版本也被优化为直到它完成之后才释放/清理内存(或推动该步骤直到程序完成之后),那么我相信你会看到 C++ 速度的提升。

测试 GC:要查看“延迟”的 .NET GC 行为,请在对象的一些析构函数/终结器方法中放置一个断点。调试器将在程序完成后激活并命中这些断点(是的,在 Main 完成后)。

推测2

否则,C# 源代码由程序员编译为 IL 代码(Microsoft 字节码指令),然后在运行时由 CLR 的即时编译器编译为特定于处理器的指令集(与经典编译程序),因此 .NET 程序一旦开始运行并第一次运行就没有理由变慢。

于 2010-02-18T03:14:47.767 回答
-2

我认为这里的每个人都错过了使一切变得不同的“秘密成分”:JIT 编译器确切地知道目标架构是什么,而静态编译器则不知道。不同的 x86 处理器具有非常不同的架构和管道,因此在一个 CPU 上可能最快的指令序列在另一个 CPU 上可能相对较慢。

在这种情况下,Microsoft C++ 编译器的优化策略针对的是与 acidzombie24 实际使用的 CPU 不同的处理器,但 gcc 选择了更适合他的 CPU 的指令。在较新、较旧或不同制造商的 CPU 上,Microsoft C++ 可能会比 gcc 更快。

JIT 具有最大的潜力:因为它确切地知道目标 CPU 是什么,所以它能够在任何情况下生成最好的代码。 因此,对于此类代码,C# 本质上(从长远来看)可能比 C++ 更快。

话虽如此,我猜 CLR 的 JIT 选择了比 Microsoft C++ 更好的指令序列这一事实更多的是运气而不是了解架构。事实证明,在 Justicle 的 CPU 上,Microsoft C++ 编译器选择了比 CLR JIT 编译器更好的指令序列。

关于 _rdtsc 与 QueryPerformanceCounter 的注释: 是的 _rdtsc 已损坏,但是当您谈论 3-4 秒的操作并多次运行以验证一致的时序时,任何导致 _rdtsc 给出虚假时序的情况(例如处理器速度变化或处理器更改)应该会导致测试数据中的异常值被丢弃,因此假设 acidzombie24 正确执行了他的原始基准测试,我怀疑 _rdtsc 与 QueryPerformanceCounter 问题是否真的有任何影响。

于 2010-02-18T06:02:11.733 回答