12

IDEの外部で実行していることを確認してください。それが鍵です。

-編集-私はSLaksのコメントが大好きです。「これらの回答の誤った情報の量は驚異的です。」:D

みんな落ち着いて。ほぼ全員が間違っていました。私は最適化を行いました。 私が行った最適化は十分ではなかったことがわかりました。 gettimeofdayを使用してGCCでコードを実行し(以下にコードを貼り付けます)、g++ -O2 file.cppC#よりもわずかに高速な結果を取得しました。 たぶんMSはこの特定のケースで必要な最適化を作成しませんでしたが、mingwをダウンロードしてインストールした後、私はテストされ、速度がほぼ同じであることがわかりました。 正義は正しいようです。PCで時計を使用することを誓い、それを使用してカウントしたところ、速度は遅くなりましたが、問題は解決しました。C ++の速度は、MSコンパイラではほぼ2倍遅くはありません。

私の友人がこれを私に知らせたとき、私はそれを信じることができませんでした。それで私は彼のコードを取り、それにいくつかのタイマーを置きました。

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;
}
4

13 に答える 13

16

編集: TL/DR バージョン: CLR JIT は 1 レベルの再帰をインライン化しますが、MSVC 8 SP1 は#pragma inline_recursion(on). また、完全に最適化された JIT を取得するには、デバッガーの外部で C# バージョンを実行する必要があります。

「高パフォーマンス」の電力設定 (~1600 ms 対 ~3800 ms) でプラグインされた Vista を実行している Core2 Duo ラップトップで VS 2008 SP1 を使用して、C# と C++ を使用した acidzombie24 と同様の結果が得られました (~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。もう 1 レベル展開します。

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 は再帰呼び出しを 1 レベルインライン化するように見えますが、C++ コンパイラはデフォルトではそうしません。

すべてのパフォーマンス クリティカルなコードがfib!

于 2010-02-18T07:21:14.693 に答える
8

編集: 元の C++ タイミングは間違っていますが (サイクルをミリ秒と比較)、より良いタイミングは、バニラ コンパイラ設定で C# がより高速であることを示しています。

わかりました、ランダムな推測は十分です。科学の時間です。既存の 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によると、間違ったベンチマーク結果が得られる可能性があります。

引用:

これにより、時間の一貫性が向上しますが、OS がプロセッサをより高いレートに切り替える前に、一定量のスピンアップ時間が低いクロック レートで費やされる場合、ベンチマークが歪む可能性があります。これには、通常よりも多くのプロセッサ サイクルが必要であるように見える効果があります。

于 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++ よりも潜在的に高速になる可能性があると主張しています。この問題を修正するキーワード restrict 。[10] メモリ割り当てに malloc/new の標準実装を無制限に使用する C++ 実装と比較して、Java ガベージ コレクションの実装は、通常、割り当てが順次行われるため、キャッシュの一貫性が向上する場合があります。* ランタイム コンパイルは、コードが実行されるプロセッサを知るなど、コードをより効果的に最適化するために、実行時に利用可能な追加情報を使用できる可能性があります。

これは Java に関するものですが、C ランタイムと JITed ランタイムの間のパフォーマンスの問題に取り組み始めています。

于 2010-02-18T02:37:38.977 に答える
2

C# は再帰呼び出しでスタックをアンロールできるのでしょうか? 計算量も減ると思います。

于 2010-02-18T02:47:09.920 に答える
1

言語を比較する際に覚えておくべき重要なことの 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();
}

これは、再帰呼び出しの 2 つの別個のセットではなく、最後の項の計算を利用して次の項を構築するためにボトムアップで機能するため、はるかに優れています。

そして、本当に 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#コードで静的関数を呼び出し、c++では非静的関数を使用します。私はC++で約1.4秒あります。g ++ -O3を使用すると、1.21秒にすることができます。

ひどく翻訳されたコードでc#とc++を比較することはできません

于 2010-07-21T19:02:05.273 に答える
0

テストを実行する前に、実行時にメソッドが事前にジットされている可能性があります...または、C++のコードcoutがバッファリングされている場合、コンソールはコンソールに出力するためのAPIのラッパーです..私は推測します..

これがお役に立てば幸いです。よろしくお願いします、トム。

于 2010-02-18T02:27:12.707 に答える
-2

ここにいる誰もが、すべての違いを生む「秘密の要素」を見逃していると思います。JITコンパイラはターゲットアーキテクチャが何であるかを正確に知っていますが、静的コンパイラはそうではありません。x86プロセッサが異なれば、アーキテクチャとパイプラインも大きく異なるため、あるCPUで可能な限り最速の一連の命令は、別のCPUでは比較的遅くなる可能性があります。

この場合、Microsoft C ++コンパイラの最適化戦略は、acidzombie24が実際に使用していたCPUとは異なるプロセッサを対象としていましたが、gccは自分のCPUにより適した命令を選択しました。新しい、古い、または異なるメーカーのCPUでは、MicrosoftC++はgccよりも高速である可能性があります。

JITには、すべての中で最高の可能性があります。対象となるCPUを正確に把握しているため、あらゆる状況で可能な限り最高のコードを生成することができます。 したがって、C#は本質的に(長期的には)そのようなコードではC++よりも高速である可能性があります。

そうは言っても、CLRのJITがMicrosoft C ++よりも優れた命令シーケンスを選択したという事実は、アーキテクチャを知ることよりも運の問題だったと思います。これは、JusticleのCPUで、MicrosoftC++コンパイラがCLRJITコンパイラよりも優れた命令シーケンスを選択したという事実によって証明されています。

A note on _rdtsc vs QueryPerformanceCounter: Yes _rdtsc is broken, but when you're talking a 3-4 second operation and running it several times to validate consistent timing, any situation that causes _rdtsc to give bogus timings (such as processor speed changes or processor changes) should cause outlying values in the test data that will be thrown out, so assuming acidzombie24 did his original benchmarks properly I doubt the _rdtsc vs QueryPerformanceCounter question really had any impact.

于 2010-02-18T06:02:11.733 に答える
-2

そのコードが実際に実行時間の 1/2 である場合、いくつかの理由が考えられます。

  • ガベージ コレクションは、上記のコードのどこかで発生している場合、C++ コードよりも C# コードの実行を高速化します。
  • コンソールへの C# の書き込みはバッファリングされる場合があります (C++ はバッファリングされないか、効率的ではない可能性があります)。
于 2010-02-18T02:23:02.037 に答える
-2

.NET コンパイラには Intel の最適化があることを知っています。

于 2010-02-18T02:47:20.420 に答える
-2

推測1

ガベージ コレクション手順が役割を果たす可能性があります。

C++ バージョンでは、プログラムの実行中にすべてのメモリ管理がインラインで行われ、それが最終時間にカウントされます。

.NET では、共通言語ランタイム (CLR) のガベージ コレクター (GC) は別のスレッド上の別のプロセスであり、多くの場合、完了後にプログラムをクリーンアップします。したがって、プログラムは終了し、メモリが解放される前に時間が出力されます。特に、通常は完了するまでまったくクリーンアップされない小さなプログラムの場合。

すべてはガベージ コレクションの実装の詳細に依存しますが (ヒープと同じようにスタックを最適化するかどうか)、これが速度向上に部分的な役割を果たしていると思います。C++ バージョンが、メモリが終了するまでメモリの割り当てを解除/クリーンアップしないように最適化されている場合 (または、プログラムが完了するまでそのステップをプッシュしない場合)、C++ の速度が向上するはずです。

GC をテストするには:「遅延した」.NET GC の動作を確認するには、オブジェクトのデストラクタ/ファイナライザ メソッドのいくつかにブレークポイントを設定します。プログラムが完了した後 (はい、Main が完了した後)、デバッガーが起動し、これらのブレークポイントにヒットします。

推測2

それ以外の場合、C# ソース コードはプログラマによって IL コード (Microsoft バイト コード命令) にコンパイルされ、実行時に CLR の Just-In-Time コンパイラによってプロセッサ固有の命令セットにコンパイルされます (従来のコンパイル済み命令と同様)。プログラム) したがって、.NET プログラムが開始されて初めて実行されると、速度が遅くなる必要はまったくありません。

于 2010-02-18T03:14:47.767 に答える