确保您在IDE之外运行。这是关键。
-edit-我喜欢SLaks评论。 “这些答案中的错误信息量是惊人的。” :d
冷静下来。几乎所有人都错了。我做了优化。
事实证明,我所做的任何优化都不够好。
我使用gettimeofday在GCC中运行代码(我将在下面粘贴代码)并使用g++ -O2 file.cpp
并获得比C#略快的结果。
也许MS没有创建这个特定情况下所需的优化,但在下载并安装mingw后,我进行了测试,发现速度几乎相同。
Justicle似乎是对的。我可以发誓我在我的电脑上使用时钟并使用它来计算并发现它速度较慢但问题已解决。在MS编译器中,C ++速度几乎不会慢两倍。
当我的朋友告诉我这件事时我无法相信。所以我拿了他的代码并把一些定时器放在上面。
而不是Boo我使用了C#。我不断在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;
}
}
}
}
GCC版本:
#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;
}
答案 0 :(得分:16)
编辑:TL / DR版本:CLR JIT将内联一级递归,MSVC 8 SP1不会没有#pragma inline_recursion(on)
。您应该在调试器之外运行C#版本以获得完全优化的JIT。
我在使用VS 2008 SP1的C#与C ++上使用VS 2008 SP1获得了类似的结果,运行Vista的Core2 Duo笔记本电脑插入了“高性能”电源设置(~1600 ms vs.~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)
。对于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
!
答案 1 :(得分: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 ++代码(带有rdtsc)没有返回毫秒,只是报告时钟周期的一个因素,因此直接与StopWatch()
结果进行比较是无效的。 原始计时代码是错误的。
注意StopWatch()
使用QueryPerformance *调用:
http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx
所以在这种情况下,C ++比C#更快。
这取决于您的编译器设置 - 请参阅MSN的答案。
答案 2 :(得分:4)
不了解垃圾收集或控制台缓冲的答案。
可能是C ++中的计时器机制本质上存在缺陷。
根据http://en.wikipedia.org/wiki/Rdtsc,您可能会得到错误的基准测试结果。
引用:
虽然这会让时间更长 一致,它可以扭曲基准, 其中有一定量的旋转时间 以前用于较低的时钟频率 操作系统将处理器切换到 更高的比率。这有效果 让事情看起来像是他们需要的 比它们更多的处理器周期 通常会。
答案 3 :(得分:4)
我认为问题在于您在C ++中的计时代码。
来自__rdtsc
的MS文档:
生成rdtsc指令,返回处理器时间戳。 处理器时间戳记录自上次复位后时钟周期的数量。
也许尝试GetTickCount()
。
答案 4 :(得分:3)
不是说这是问题,但您可能需要阅读How to: Use the High-Resolution Timer
另见...... 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运行时之间的性能问题。
答案 5 :(得分:2)
也许C#能够在递归调用中展开堆栈?我认为这也减少了计算次数。
答案 6 :(得分: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;
};
答案 7 :(得分:0)
可能是这些方法在运行测试之前在运行时进行预先处理...或者当C ++的cout
代码被缓冲时,Console是API的输出到控制台的包装器..我猜..
希望这有帮助, 最好的祝福, 汤姆。
答案 8 :(得分:0)
你在c#代码中调用静态函数,它将被内联,而在c ++中你使用非静态函数。我有~1.4秒的c ++。使用g ++ -O3,你可以拥有1.21秒。
你无法将c#与c ++与错误翻译的代码进行比较
答案 9 :(得分:-2)
如果该代码确实是执行时间的1/2,那么可能的原因是:
答案 10 :(得分:-2)
我知道.NET编译器具有Intel优化功能。
答案 11 :(得分:-2)
垃圾收集程序可能会发挥作用。
在C ++版本中,所有内存管理都会在程序运行时内联进行,这将计入最后一次。
在.NET中,公共语言运行时(CLR)的垃圾收集器(GC)是一个不同线程上的独立进程,并且通常在程序完成后清理它。因此,您的程序将完成,在释放内存之前将打印出时间。特别是对于通常在完成之前通常不会被清理的小程序。
这完全取决于垃圾收集实现的细节(如果它以与堆相同的方式优化堆栈),但我认为这在速度增益中起着部分作用。如果C ++版本也被优化为在完成之前不释放/清理内存(或者在程序完成之后推送该步骤),那么我相信你会看到C ++的速度提升。
测试GC:要查看“延迟的”.NET GC行为,请在您的某些对象的析构函数/终结器方法中放置一个断点。程序完成后调试器将变为活动状态并触发这些断点(是的,在Main完成后)。
否则,程序员将C#源代码编译到IL代码(Microsoft字节代码指令),并在运行时由CLR的Just-In-Time编译器编译成一个特定于处理器的指令集(与经典编译程序一样)因此,一旦.NET程序开始运行并且第一次运行,它就没有理由变慢。
答案 12 :(得分:-2)
我认为这里的每个人都错过了“秘密成分”,这就完全不同了:JIT编译器确切地知道目标体系结构是什么,而静态编译器却不知道。不同的x86处理器具有非常不同的体系结构和流水线,因此在一个CPU上尽可能快的指令序列在另一个CPU上可能相对较慢。
在这种情况下,Microsoft C ++编译器的优化策略针对的是与CPU acidzombie24实际使用的处理器不同的处理器,但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问题确实有任何影响。