2012-01-02 3 views
3

Benchmark Код:Mono 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"); 
    } 
} 
} 

Environment Setup:

для Windows 7/Mono 2.10.8/MonoDevelop 2.8.5

MonoDevelop Установка:

  • Инструменты> Параметры> .NET R untimes> Mono 2.10.8 (по умолчанию)
  • Project> Параметры> Построение> Общие> Целевые рамки> Mono/.NET 4,0
  • Project> Options> Build> Compiler> Общие параметры> Включить оптимизацию
  • Project> Опции> Build> Compiler> Общие параметры> целевой платформы> x86
  • Project> Options> Run> Общие> параметры> -O = SIMD

Результаты:

  • 94,4 нс
  • 29,7 нс
  • 49,9 нс
  • 231595,2 нс
+0

Что касается вашего EDIT 1: не обязательно. Только для вас я установил вашу версию на Mono и binutils на машине Windows 7, чтобы проверить собственный код, созданный с помощью -O = simd и -O = -simd. И получается, что поддержка SIMD задействована в обоих случаях;) Вы можете опубликовать сообщение об ошибке bugzilla от Mono, что поддержка SIMD не может быть отключена ** в Windows с использованием -O или -optimize. Вы также можете увидеть мой последний комментарий под своим ответом. –

ответ

1

Ну, мне удалось изменить свой контрольный код, чтобы сделать его более надежным и полностью unbiased. Другими словами:

Во-первых, как мы обсуждали с Николасом - измерение одной операции может дать искаженные результаты. Более того, поскольку частота секундомера составляет 10 миллионов, это означает, что тики происходят каждые 100 нс. Поэтому, учитывая этот факт, предыдущие результаты выглядят довольно странно. Поэтому, чтобы смягчить эту проблему, я решил протестировать 1000 операций, а не 1 раз.

Во-вторых, я не совсем уверен, но я предполагаю, что предыдущая эталонная реализация подвергалась интенсивному кешированию, поскольку на каждой итерационной сумме вычислялись между одними и теми же векторами (их компоненты никогда не менялись). Единственное прямое решение, которое я вижу, - просто перестроить векторы со случайными компонентами перед каждым тестом.

Соответствующая реализация является ориентиром:

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 нс
  • 2677,4 нс
  • 20138,4 нс
  • 597581060.7 нс

Я думаю, что это подтверждает, что SIMD в эфире, потому что SimdBenchmark приближается к SimpleBenchmark (как задумано по технологии SIMD) и намного лучше, чем DefaultBenchmark (опять же, как подразумевается технология SIMD).

Кроме того, результаты, кажется, согласуется с konrad.kruczynski, поскольку соотношение между SimdBenchmark (3203.9) и DefaultBenchmark (20138,4) составляет около 6, а соотношение между simdVector (5802) и usualVector (29598) также около 6.

Во всяком случае 2 вопроса по-прежнему остаются:

  1. Почему играть с "-O = SIMD"/"-O = -simd" не имеет никакого эффекта. Это устарело? Включен ли SIMD?
  2. Как мог Секундомер с клещей 100 нс давать предыдущие результаты (94,4, 29,7, 49,9), которые, очевидно, ниже 100 нс?
+0

Для вашего первого вопроса: пожалуйста, прочитайте мой ответ от «О, и для полноты» он работает на моей машине. Вы можете проверить, все ли SIMD все еще испущены с помощью --optimize = -simd, и если они есть, отправьте сообщение об ошибке. –

+0

Они переключались с «O» на «оптимизировать»? –

+0

нет, -O также работает на моей ОС. Я не уверен, правильно ли я понял вас, но simd включены по умолчанию, однако вы должны отключить их, используя -O = -simd или -optimize = -simd. Что касается вашего второго вопроса: это будет довольно странно * независимо от частоты секундомера, потому что вы используете свойство Elapsed. Истекший тип типа TimeSpan, а наименьший временной интервал - один TimeSpan tick = 100 нс. Причиной наблюдаемых значений является ** усреднение **. Распечатайте все значения вместо среднего, вы не заметите ничего странного. –

6

Я подозреваю вашу базовую инфраструктуру первой.

Пар точек может быть:

  • Вы используете `Stopwatch` для времени отдельных операций - это не разрешение
  • Ваших тайминги включают вызов, виртуальную функцию
  • Ваших размер выборки (1000) слишком мал
+0

Накладные расходы виртуального вызова бесконечно малые - уже проверены. Если секундомер не имеет разрешения, то мы получим одинаковые результаты везде (кроме ужасных MathNet). 1000 более чем достаточно, чтобы выяснить, насколько SimdBenchmark, по крайней мере, примерно близок к SimpleBecnhmark (как это подразумевается технологией) И значительно лучше, чем DefaultBenchmark (также подразумевается). –

+0

Эй! Просто пытаюсь помочь здесь :) Согласуются ли ваши результаты с результатами тестовых прогонов? Что возвращает 'секундомер.Frequency'? –

+0

Да, они согласуются большую часть времени (например, 9/10). Частота составляет 10 миллионов - это означает, что тики происходят каждые 100 нс (и это на самом деле странно, учитывая результаты) ... xD –

6

это мои результаты:

1608.8 ns 
1554.9 ns 
1582.5 ns 

(без MathNET, хотя здесь это не важно). Os - Ubuntu 10.10 (32 бит), Mono 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, поскольку, вероятно, он использует disas из GCC (вам может быть больше удачи с помощью cygwin). Прочитав такую ​​сборку, вы можете проверить, действительно ли работают SIMD-операции.

Например, рассмотрев сборку, сгенерированную для вышеуказанной вставной программы, можно обнаружить, что она использует инструкцию addps в своем цикле SIMD, что и есть то, что мы ищем здесь.

О, и для полноты здесь выводится с SIMD-инвалида:

$ 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 операций.

Надеюсь, это было полезно.