在这种情况下,为什么.NET比C ++更快?

确保您在IDE外部运行。 这是关键。

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

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

当我的朋友告诉我这件事时我无法相信。 所以我拿了他的代码并把一些定时器放在上面。

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

C ++版(坏版):

 #include  #include  #include  #include  using namespace std; int fib(int n) { if (n > 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  #include  #include  #include  using namespace std; int fib(int n) { if (n > 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 > n; n = 41; if (n < 0) break; timer.Start(); int res = fib(n); timer.Stop(); Console.WriteLine(res); Console.WriteLine(timer.ElapsedMilliseconds); break; } } } } 

GCC版本:

 #include  #include  #include  using namespace std; int fib(int n) { if (n > 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; } 

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

我使用VS 2008 SP1在运行带有“高性能”电源设置的插入Vista的Core2 Duo笔记本电脑上使用VS 2008 SP1获得了类似于使用C#与C ++的相似结果(~1600 ms与~3800 ms)。 看到优化的JIT的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) 。 对于一个如此强大的函数,减少函数调用的数量是速度的关键。 用以下内容替换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

编辑:虽然最初的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 < 

编辑:

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的答案。

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

可能是C ++中的计时器机制本质上存在缺陷。

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

引:

虽然这使得时间保持更加一致,但它可能会使基准测试偏差,其中在OS将处理器切换到更高速率之前以较低的时钟速率花费一定量的旋转时间。 这使得事情看起来像需要比通常更多的处理器周期。

我认为问题是你在C ++中的计时代码。

来自__rdtsc的MS文档:

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

也许试试GetTickCount()

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

另见… 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运行时之间的性能问题。

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

比较语言时要记住的一件重要事情是,如果你做一个简单的逐行翻译,你就不会比较苹果和苹果。

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

 IEnumerable 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 struct fibonacci { static const int value = fibonacci::value + fibonacci::value; }; template<> struct fibonacci<1> { static const int value = 1; }; template<> struct fibonacci<0> { static const int value = 0; }; 

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

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

你在c#代码中调用静态函数,它将被内联,在c ++中你使用非静态函数。 我有~1.4秒的c ++。 使用g ++ -O3,你可以拥有1.21秒。

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

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

  • 如果在上面的代码中的任何地方发生垃圾收集,那么垃圾收集会加速C#代码在C ++代码上的执行。
  • 写入控制台的C#可能是缓冲的(C ++可能没有,或者可能效率不高)

猜测1

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

在C ++版本中,所有内存管理都将在程序运行时以内联方式进行,这将计入最后时间。

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

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

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

猜测2

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

我认为这里的每个人都错过了“秘密成分” ,它们完全不同:JIT编译器确切地知道目标体系结构是什么,而静态编译器却不知道。 不同的x86处理器具有非常不同的体系结构和流水线,因此在一个CPU上尽可能最快的一系列指令在另一个CPU上可能相对较慢。

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

JIT具有最好的潜力:因为它确切地知道什么是CPU,所以它能够在每种情况下生成最好的代码。 因此,对于这样的代码,C#本质上(在长期内)可能比C ++更快。

说到这一点之后,我猜想CLR的JIT选择比Microsoft C ++更好的指令序列这一事实更多的是运气而不是了解架构。 事实certificate,在Justicle的CPU上,Microsoft C ++编译器选择了比CLR JIT编译器更好的指令序列。

有关_rdtsc与QueryPerformanceCounter的说明:是_rdtsc已损坏,但是当您说3-4秒操作并多次运行以validation一致的时序时,任何导致_rdtsc给出伪造时序的情况(例如处理器速度变化或处理器更改)应该导致测试数据中的偏离值被抛出,所以假设acidzombie24正确地完成了他原来的基准测试我怀疑_rdtsc vs QueryPerformanceCounter问题确实有任何影响。

我知道.NET编译器具有Intel优化function。