2017-06-03 38 views
9

私は、単一のフロートをラップする構造体が、フロートを直接使用するよりもパフォーマンスの約半分で大幅に遅いことに気付きました。structに余分なフィールドを追加すると、パフォーマンスが大幅に向上するのはなぜですか?

using System; 
using System.Diagnostics; 

struct Vector1 { 

    public float X; 

    public Vector1(float x) { 
     X = x; 
    } 

    public static Vector1 operator +(Vector1 a, Vector1 b) { 
     a.X = a.X + b.X; 
     return a; 
    } 
} 

しかし、追加された「余分な」フィールドを追加すると、いくつかの魔法が再び起こるとパフォーマンスのためには、より合理的になりそうです:

struct Vector1Magic { 

    public float X; 
    private bool magic; 

    public Vector1Magic(float x) { 
     X = x; 
     magic = true; 
    } 

    public static Vector1Magic operator +(Vector1Magic a, Vector1Magic b) { 
     a.X = a.X + b.X; 
     return a; 
    } 
} 

次のように私がベンチマークにこれらを使用するコードは次のとおりです。

ベンチマークの結果では
class Program { 
    static void Main(string[] args) { 
     int iterationCount = 1000000000; 
     var sw = new Stopwatch(); 
     sw.Start(); 
     var total = 0.0f; 
     for (int i = 0; i < iterationCount; i++) { 
      var v = (float) i; 
      total = total + v; 
     } 
     sw.Stop(); 
     Console.WriteLine("Float time was {0} for {1} iterations.", sw.Elapsed, iterationCount); 
     Console.WriteLine("total = {0}", total); 
     sw.Reset(); 
     sw.Start(); 
     var totalV = new Vector1(0.0f); 
     for (int i = 0; i < iterationCount; i++) { 
      var v = new Vector1(i); 
      totalV += v; 
     } 
     sw.Stop(); 
     Console.WriteLine("Vector1 time was {0} for {1} iterations.", sw.Elapsed, iterationCount); 
     Console.WriteLine("totalV = {0}", totalV); 
     sw.Reset(); 
     sw.Start(); 
     var totalVm = new Vector1Magic(0.0f); 
     for (int i = 0; i < iterationCount; i++) { 
      var vm = new Vector1Magic(i); 
      totalVm += vm; 
     } 
     sw.Stop(); 
     Console.WriteLine("Vector1Magic time was {0} for {1} iterations.", sw.Elapsed, iterationCount); 
     Console.WriteLine("totalVm = {0}", totalVm); 
     Console.Read(); 
    } 
} 

Float time was 00:00:02.2444910 for 1000000000 iterations. 
Vector1 time was 00:00:04.4490656 for 1000000000 iterations. 
Vector1Magic time was 00:00:02.2262701 for 1000000000 iterations. 

コンパイラ/環境設定: OS:Windowsの10 64ビット ツールチェーン:VS2017 フレームワーク:.NET 4.6.2 ターゲット:64ビットがターゲットとして設定されている場合は、任意のCPUは、我々の結果を32ビット

を好みますより予測可能ですが、我々は32ビットターゲットにVector1Magicで見るものよりも大幅に悪化:本当の魔法使いのために

Float time was 00:00:00.6800014 for 1000000000 iterations. 
Vector1 time was 00:00:04.4572642 for 1000000000 iterations. 
Vector1Magic time was 00:00:05.7806399 for 1000000000 iterations. 

、私はここでILのダンプを含めました:https://pastebin.com/sz2QLGEx

を詳細な調査では、モノラルコンパイラは同じILを生成するので、これはWindowsランタイムに固有のようです。

モノラルランタイムでは、両方の構造体バリアントが、生の浮動小数点に比べて約2倍遅い性能を持ちます。これは、.Netで見られるパフォーマンスとはかなり異なっています。

ここでは何が起こっていますか?

*この質問には当初は欠陥のあるベンチマークプロセスが含まれていました(これを指摘してくれたマックスペイン氏に感謝します)。タイミングをより正確に反映するように更新されました。

+1

イムを、これは今より良いメモリ配置を持つ梱包構造体によるものである推測:

は、7年前anseredたようです。 –

+2

JITまたは他のワンタイム処理からの干渉を排除するためにウォーミングアップ反復を追加する必要があります。 – PetSerAl

+2

64ビットに切り替えると、「マジック」ベクタのパフォーマンスが低下します。 – Adrian

答えて

0

これは起こりません。これは明らかにJITを強制的に動作させないようなミスアラインメントです。

struct Vector1 //Works fast in 32 Bit 
{ 
    public double X; 
} 

struct Vector1 //Works fast in 64 Bit and 32 Bit 
{ 
    public double X; 
    public double X2; 
} 

また、呼び出す必要があります: ます。Console.WriteLine(合計)。は、Vector1Magic時間に正確に時間を増やします。なぜなら、なぜVector1が非常に遅いのかという疑問がまだあります。

構造体がsizeof(foo)に最適化されていない可能性があります。< 64ビットモードの64ビット。 Why is 16 byte the recommended size for struct in C#?

+0

"これは起こらないはずです。これは明らかに、JITを強制的に動作させない何らかのミスアラインメントです。" - これは本当に質問に答えるものではありません。なぜこれが起こるのですか?これの背後にある理由は何ですか? – Varon

+0

その後、誰かがネットをどのように内部的に動作させるか知っている誰かが来るまで、upvoteを待ちます。生成されたILコードは良いので、私たちに解決策を教えてくれることはありません。問題はJITオプティマイザの内部で深くなります。これは非常に興味深い発見です、多分MSDN .net開発者チームフォーラムでこれを投稿できますか? –

+0

Console.WriteLine(total)という行を挿入してください。あなたの最初のループの後。 JITは結果を使用しないノードを実行しません。 –

関連する問題