2012-01-02 10 views
3

ベンチマークコード:モノSIMDのパフォーマンスが悪化していますか?

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using Mono.Simd; 
using MathNet.Numerics.LinearAlgebra.Single; 

namespace XXX { 
public static class TimeSpanExtensions { 
    public static double TotalNanoseconds(this TimeSpan timeSpan) { 
     return timeSpan.TotalMilliseconds * 1000000.0; 
    } 
} 

public sealed class SimdBenchmark : Benchmark { 
    Vector4f a = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f); 
    Vector4f b = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f); 
    Vector4f c; 

    public override void Do() { 
     c = a + b; 
    } 
} 

public sealed class MathNetBenchmark : Benchmark { 
    DenseVector a = new DenseVector(new float[]{1.0f,2.0f,3.0f,4.0f}); 
    DenseVector b = new DenseVector(new float[]{1.0f,2.0f,3.0f,4.0f}); 
    DenseVector c; 

    public override void Do() { 
     c = a + b; 
    } 
} 

public sealed class DefaultBenchmark : Benchmark { 
    Vector4 a = new Vector4(1.0f, 2.0f, 3.0f, 4.0f); 
    Vector4 b = new Vector4(1.0f, 2.0f, 3.0f, 4.0f); 
    Vector4 c; 

    public override void Do() { 
     c = a + b; 
    } 
} 

public sealed class SimpleBenchmark : Benchmark { 
    float a = 1.0f; 
    float b = 2.0f; 
    float c; 

    public override void Do() { 
     c = a + b; 
    } 
} 

public sealed class DelegateBenchmark : Benchmark { 
    private readonly Action _action; 

    public DelegateBenchmark(Action action) { 
     _action = action; 
    } 

    public override void Do() { 
     _action(); 
    } 
} 

public abstract class Benchmark : IEnumerable<TimeSpan> { 
    public IEnumerator<TimeSpan> GetEnumerator() { 
     Do(); // Warm-up! 

     GC.Collect(); // Collect garbage. 
     GC.WaitForPendingFinalizers(); // Wait until finalizers finish. 

     var stopwatch = new Stopwatch(); 

     while (true) { 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      Do(); 
      stopwatch.Stop(); 

      yield return stopwatch.Elapsed; 
     } 
    } 

    IEnumerator IEnumerable.GetEnumerator() { 
     return GetEnumerator(); 
    } 

    public abstract void Do(); 
} 

public struct Vector4 { 
    float x; 
    float y; 
    float z; 
    float w; 

    public Vector4(float x, float y, float z, float w) { 
     this.x = x; 
     this.y = y; 
     this.z = z; 
     this.w = w; 
    } 

    public static Vector4 operator +(Vector4 v1, Vector4 v2) { 
     return new Vector4(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z, v1.w + v2.w); 
    } 
} 

class MainClass { 
    public static void Main(string[] args) { 
     var avgNS1 = new SimdBenchmark().Take(1000).Average(timeSpan => timeSpan.TotalNanoseconds()); 
     var avgNS2 = new SimpleBenchmark().Take(1000).Average(timeSpan => timeSpan.TotalNanoseconds()); 
     var avgNS3 = new DefaultBenchmark().Take(1000).Average(timeSpan => timeSpan.TotalNanoseconds()); 
     var avgNS4 = new MathNetBenchmark().Take(1000).Average(timeSpan => timeSpan.TotalNanoseconds()); 


     Console.WriteLine(avgNS1 + " ns"); 
     Console.WriteLine(avgNS2 + " ns"); 
     Console.WriteLine(avgNS3 + " ns"); 
     Console.WriteLine(avgNS4 + " ns"); 
    } 
} 
} 

環境設定:

のWindows 7 /モノラル2.10.8/MonoDevelopの2.8.5

MonoDevelopのセットアップ:

  • ツール>オプション> .NET R untimes>モノ2.10.8(デフォルト)オプション>>一般>ターゲットフレームワーク>モノラル/ .NET 4.0
  • プロジェクト]> [オプション]> [ビルド・>コンパイラ>一般オプション>
  • 最適化を有効にし、プロジェクトをビルド>
  • プロジェクト> [オプション]> [ビルド・>コンパイラ]> [一般オプション]> [プラットフォームターゲット> x86の
  • プロジェクト]> [オプション]> [ファイル名を指定して実行>一般>パラメータ> -O = SIMD

結果:

私が最初にあなたのベンチマークのインフラストラクチャを疑うだろう
  • 94.4 nsの
  • 29.7 nsの
  • 49.9 nsの
  • 231595.2 NS
+0

EDIT 1については、必ずしもそうではありません。ちょうどあなたのために、Windows 7マシンのMonoとbinutilsにバージョンをインストールして、-O = simdと-O = -simdで生成されたネイティブコードをチェックアウトしました。また、SIMDサポートは両方のケースで関わっていることが判明しました;)Monoのbugzillaに、-Oまたは--optimizeフラグを使用してSIMDサポートを無効にすることはできません。あなたの答えの下で私の最後のコメントを見ることもできます。 –

答えて

1

私はベンチマークコードを変更して、より堅牢で完全ににすることができました。言い換えれば、

最初に、私たちがNicholasと議論したように、単一操作を測定すると歪んだ結果が出る可能性があります。さらに、Stopwatchの頻度が1000万であるため、100nsごとにダニが発生することを意味します。この事実を考えると、以前の結果はかなり奇妙に見えます。したがって、この問題を軽減するために、私は時間で1ではなく1000の操作をテストすることに決めました。

第2に、私は完全にはわかりませんが、以前のベンチマークの実装は集中的なキャッシュを受けていたと思います。なぜなら、すべての繰り返しで同じベクトル間で合計が計算されるからです。私が見る唯一の簡単な解決策は、すべてのテストの前に無作為のコンポーネントでベクトルを単純に再構築することです。

それぞれのベンチマークの実装は次のとおりです。

public static class TimeSpanExtensions { 
    public static double TotalNanoseconds(this TimeSpan timeSpan) { 
     return timeSpan.TotalMilliseconds * 1000000.0; 
    } 
} 

public static class RandomExtensions { 
    public static float NextFloat(this Random random) { 
     return (float)random.NextDouble(); 
    } 

    public static float NextFloat(this Random random, float min, float max) { 
     return random.NextFloat() * (max - min) + min; 
    } 
} 

public sealed class SimdBenchmark : Benchmark { 
    Vector4f[] a = new Vector4f[1000]; 
    Vector4f[] b = new Vector4f[1000]; 
    Vector4f[] c = new Vector4f[1000]; 

    public override void Begin() { 
     Random r = new Random(); 

     for (int i = 0; i < 1000; ++i) { 
      a[i] = new Vector4f(r.NextFloat(), r.NextFloat(), r.NextFloat(), r.NextFloat()); 
      b[i] = new Vector4f(r.NextFloat(), r.NextFloat(), r.NextFloat(), r.NextFloat()); 
     } 
    } 

    public override void Do() { 
     for (int i = 0; i < 1000; ++i) 
      c[i] = a[i] + b[i]; 
    } 

    public override void End() { 

    } 
} 

public sealed class MathNetBenchmark : Benchmark { 
    DenseVector[] a = new DenseVector[1000]; 
    DenseVector[] b = new DenseVector[1000]; 
    DenseVector[] c = new DenseVector[1000]; 

    public override void Begin() { 
     Random r = new Random(); 

     for (int i = 0; i < 1000; ++i) { 
      a[i] = new DenseVector(new float[]{r.NextFloat(), r.NextFloat(), r.NextFloat(), r.NextFloat()}); 
      b[i] = new DenseVector(new float[]{r.NextFloat(), r.NextFloat(), r.NextFloat(), r.NextFloat()}); 
     } 
    } 

    public override void Do() { 
     for (int i = 0; i < 1000; ++i) 
      c[i] = a[i] + b[i]; 
    } 

    public override void End() { 

    } 
} 

public sealed class DefaultBenchmark : Benchmark { 
    Vector4[] a = new Vector4[1000]; 
    Vector4[] b = new Vector4[1000]; 
    Vector4[] c = new Vector4[1000]; 

    public override void Begin() { 
     Random r = new Random(); 

     for (int i = 0; i < 1000; ++i) { 
      a[i] = new Vector4(r.NextFloat(), r.NextFloat(), r.NextFloat(), r.NextFloat()); 
      b[i] = new Vector4(r.NextFloat(), r.NextFloat(), r.NextFloat(), r.NextFloat()); 
     } 
    } 

    public override void Do() { 
     for (int i = 0; i < 1000; ++i) 
      c[i] = a[i] + b[i]; 
    } 

    public override void End() { 

    } 
} 

public sealed class SimpleBenchmark : Benchmark { 
    float[] a = new float[1000]; 
    float[] b = new float[1000]; 
    float[] c = new float[1000]; 

    public override void Begin() { 
     Random r = new Random(); 

     for (int i = 0; i < 1000; ++i) { 
      a[i] = r.NextFloat(); 
      b[i] = r.NextFloat(); 
     } 
    } 

    public override void Do() { 
     for (int i = 0; i < 1000; ++i) 
      c[i] = a[i] + b[i]; 
    } 

    public override void End() { 

    } 
} 

public sealed class DelegateBenchmark : Benchmark { 
    private readonly Action _action; 

    public DelegateBenchmark(Action action) { 
     _action = action; 
    } 

    public override void Begin() { 

    } 

    public override void Do() { 
     _action(); 
    } 

    public override void End() { 

    } 
} 

public abstract class Benchmark : IEnumerable<TimeSpan> { 
    public IEnumerator<TimeSpan> GetEnumerator() { 
     Begin(); 
     Do(); // Warm-up! 
     End(); 

     var stopwatch = new Stopwatch(); 

     while (true) { 
      Begin(); 

      GC.Collect(); // Collect garbage. 
      GC.WaitForPendingFinalizers(); // Wait until finalizers finish. 

      stopwatch.Reset(); 
      stopwatch.Start(); 

      Do(); 

      stopwatch.Stop(); 

      End(); 

      yield return stopwatch.Elapsed; 
     } 
    } 

    IEnumerator IEnumerable.GetEnumerator() { 
     return GetEnumerator(); 
    } 

    public abstract void Begin(); 

    public abstract void Do(); 

    public abstract void End(); 
} 

public struct Vector4 { 
    float x; 
    float y; 
    float z; 
    float w; 

    public Vector4(float x, float y, float z, float w) { 
     this.x = x; 
     this.y = y; 
     this.z = z; 
     this.w = w; 
    } 

    public static Vector4 operator +(Vector4 v1, Vector4 v2) { 
     return new Vector4(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z, v1.w + v2.w); 
    } 
} 

class MainClass { 
    public static void Main(string[] args) { 
     var avgNS1 = new SimdBenchmark().Take(1000).Average(timeSpan => timeSpan.TotalNanoseconds()); 
     var avgNS2 = new SimpleBenchmark().Take(1000).Average(timeSpan => timeSpan.TotalNanoseconds()); 
     var avgNS3 = new DefaultBenchmark().Take(1000).Average(timeSpan => timeSpan.TotalNanoseconds()); 
     var avgNS4 = new MathNetBenchmark().Take(1000).Average(timeSpan => timeSpan.TotalNanoseconds()); 

     Console.WriteLine(avgNS1 + " ns"); 
     Console.WriteLine(avgNS2 + " ns"); 
     Console.WriteLine(avgNS3 + " ns"); 
     Console.WriteLine(avgNS4 + " ns"); 
    } 
} 

結果:

  • 3203.9 NS
  • 2677.4 NS
  • 20138.4 NS
  • 597581060。7 nsの

私はそれがSIMDは(SIMD技術により意図したとおり)SimdBenchmarkがSimpleBenchmarkに近づいているので、空気にあり、(SIMD技術によって暗示再びとして)DefaultBenchmarkよりもはるかに優れていることが確認さだと思います。

SimdBenchmark(3203.9)とDefaultBenchmark(20138.4)との比は約6であり、simdVector(5802)及びusualVector(29598)との間の比はまた、約6

あるためまた、結果は、konrad.kruczynskiと一致すると思われます

とにかく2つの質問がまだ残っている:

  1. "-O = SIMD"/"-O = -simd" で遊んでても何の効果もありませんなぜ。それは廃止されましたか? SIMDは自動的に使用されますか?
  2. 100 nsのダニでストップウォッチを行うと、明らかに100 nsよりも小さい以前の結果(94.4,29.7,49.9)が得られますか?
+0

あなたの最初の質問について:「ああ、完全性のために」私の答えを読んでください。 SIMDがまだ--optimize = -simdで発行されているかどうかを確認することができます。もしそうであれば、バグを投稿してください。 –

+0

"O"から "最適化"に切り替えましたか? –

+0

いいえ、-Oも私のOSで動作します。私はあなたを正しく理解しているかどうかは分かりませんが、simdはデフォルトで有効になっていますが、-O = -simdまたは--optimize = -simdを使用して無効にする必要があります。あなたの2番目の質問については、Elapsedプロパティを使用しているので、これはストップウォッチの頻度にかかわらず非常に奇妙です*。 ElapsedはTimeSpan型で、最小時間間隔はTimeSpan tick = 100 nsです。あなたが観察した値の原因は**平均**です。平均値ではなくすべての値を出力すると、何も変わっていないことに気付くはずです。 –

6

ポイントのカップルは次のようになります。

  • あなたは時間の単一の操作に `Stopwatch`を使用している - それはあなたのタイミングは、仮想関数呼び出し
  • あなたを含め解像度
  • を持っていませんサンプルサイズ(1000)は小さすぎる
+0

仮想コールオーバーヘッドは無限小です - 既にチェックされています。 ストップウォッチに解決策がない場合は、どこでも同じ結果が得られます(ただし、ひどいMathNet ofcourseは除きます)。 1000は、SimdBenchmarkがSimpleBecnhmarkに少なくともほぼ近づいているかどうか(技術が暗示しているように)、またDefaultBenchmarkよりもかなり優れているかどうかを判断するのに十分です。 –

+0

ねえ!ちょうどここでお手伝いしようとしています:)あなたの結果は一貫して試行されますか'Stopwatch.Frequency'は何を返しますか? –

+0

ええ、彼らはほとんどの時間(9/10のように)一貫しています。頻度は1,000万です - ダニが100 nsごとに発生することを意味します(実際には結果を考慮すると奇妙です)... xD –

6

これらは私の結果である:

1608.8 ns 
1554.9 ns 
1582.5 ns 

(MathNETはありませんが、ここでは重要ではありません)。 OsはUbuntu 10.10(32bit)、Mon 2.10.7です。現時点では、Windows Monoバージョンを対象としたバグレポートの作成を検討しているかもしれません。しかし:

これは、ベンチマークのメカニズムオーバーヘッドのためにSIMD操作をベンチマークする正しい方法ではないと思います。

たとえば、Vector4クラスに基づいてこのプリミティブテストを見てください。私のマシンの結果に

 const int count = 100000; 
     var simdVector = new Vector4f(1, 2, 3, 4); 
     var simdResult = simdVector; 
     var sw = Stopwatch.StartNew(); 
     for(var i = 0; i < count; i++) 
     { 
      simdResult += simdVector; 
     } 
     sw.Stop(); 
     Console.WriteLine("SIMD result: {0} {1}", sw.Elapsed, simdResult); 
     sw = Stopwatch.StartNew(); 
     var usualVector = new Vector4(1, 2, 3, 4); 
     var usualResult = usualVector; 
     for(var i = 0; i < count; i++) 
     { 
      usualResult += usualVector; 
     } 
     sw.Stop(); 
     Console.WriteLine("Usual result: {0} {1}", sw.Elapsed, usualResult); 

です:

SIMD result: 00:00:00.0005802 <100001, 200002, 300003, 400004> 
Usual result: 00:00:00.0029598 <100001, 200002, 300003, 400004> 

だからあなたのテストよりも間違いなく別の何か。だから、SIMDの操作がそれほど速いと思うかもしれませんが、ベンチマークは簡単ではありません。この構成では、上部ループが高速になる理由はたくさんあります。これらの理由は別の機会に議論することができます。

SIMD は、追加のカップルよりも速くであることが確信しています。あなたが本当に放出されているかどうかを確認する必要があります。

Linuxの場合、生成されたアセンブリ(モノアセンブリではなく、ターゲットプロセッサのアセンブリの意味で))を確認するには、mono -v -vを使用します。それにもかかわらず、通常のWindowsシステムで動作しているのかどうかは分かりませんが、おそらくGCCのdisasを使用しています(cygwinを使用すると運が増えるかもしれません)。このようなアセンブリを読むことで、SIMD操作が実際に行われているかどうかを確認できます。

たとえば、上記の貼り付けプログラム用に生成されたアセンブリを調べると、ここで探しているSIMDループにaddps命令が使用されていることがわかります。

$ mono --optimize=-simd SimdTest.exe 
SIMD result: 00:00:00.0027111 <100001, 200002, 300003, 400004> 
Usual result: 00:00:00.0026127 <100001, 200002, 300003, 400004> 

何のSIMD演算を含まない、生成されたアセンブリとしてそれほど重要ではありません:ああ、ここで完全性について

は無効SIMDで出力されます。

希望しました。

関連する問題