2014-09-16 2 views
1

В продолжении вопроса я отправил вчера, Mathematics and generics я решил пойти дальше и создать простой RealNumber класс оберточной decimal для того, чтобы общей математики и сделать несколько простых тестов, чтобы увидеть производительность по сравнению (пожалуйста не комментировать почему, зачем или как реализовать обертку для десятичной, это не цель этого вопроса).Weird тест производительность поведение

Я решил сравнить следующие 2 реализации:

  1. RealNumberStruct: интерфейс варианта
  2. RealNumberClass: опция абстрактного базового класса

Код для RealNumberStruct выглядит следующим образом:

internal interface IArithmetic : IEquatable<IArithmetic> 
{ 
    IArithmetic Add(IArithmetic right); 
    IArithmetic Subtract(IArithmetic right); 
    IArithmetic Multiply(IArithmetic right); 
    IArithmetic Divide(IArithmetic right); 
    IArithmetic Negate(); 
} 

public struct RealNumberStruct : IArithmetic 
{ 
    private readonly decimal value; 

    private RealNumberStruct(decimal d) 
    { 
     this.value = d; 
    } 

    public static implicit operator decimal(RealNumberStruct value) 
    { 
     return value.value; 
    } 

    public static implicit operator RealNumberStruct(decimal value) 
    { 
     return new RealNumberStruct(value); 
    } 

    public static RealNumberStruct operator +(RealNumberStruct left, RealNumberStruct right) 
    { 
     return new RealNumberStruct(left.value + right.value); 
    } 

    public static RealNumberStruct operator -(RealNumberStruct value) 
    { 
     return new RealNumberStruct(-value.value); 
    } 

    public static RealNumberStruct operator -(RealNumberStruct left, RealNumberStruct right) 
    { 
     return new RealNumberStruct(left.value - right.value); 
    } 

    public static RealNumberStruct operator *(RealNumberStruct left, RealNumberStruct right) 
    { 
     return new RealNumberStruct(left.value * right.value); 
    } 

    public static RealNumberStruct operator /(RealNumberStruct left, RealNumberStruct right) 
    { 
     return new RealNumberStruct(left.value/right.value); 
    } 

    IArithmetic IArithmetic.Add(IArithmetic right) 
    { 
     if (!(right is RealNumberStruct)) 
      throw new ArgumentException(); 

     return this + (RealNumberStruct)right; 
    } 

    IArithmetic IArithmetic.Subtract(IArithmetic right) 
    { 
     if (!(right is RealNumberStruct)) 
      throw new ArgumentException(); 

     return this - (RealNumberStruct)right; 
    } 

    IArithmetic IArithmetic.Multiply(IArithmetic right) 
    { 
     if (!(right is RealNumberStruct)) 
      throw new ArgumentException(); 

     return this * (RealNumberStruct)right; 
    } 

    IArithmetic IArithmetic.Divide(IArithmetic right) 
    { 
     if (!(right is RealNumberStruct)) 
      throw new ArgumentException(); 

     return this/(RealNumberStruct)right; 
    } 

    IArithmetic IArithmetic.Negate() 
    { 
     return -this; 
    } 

    bool IEquatable<IArithmetic>.Equals(IArithmetic other) 
    { 
     throw new NotImplementedException(); 
    } 
} 

Код для RealNumberClass выглядит следующим образом:

public abstract class Arithmetic: IEquatable<Arithmetic> 
{ 
    protected abstract Arithmetic _Add(Arithmetic right); 
    protected abstract Arithmetic _Subtract(Arithmetic right); 
    protected abstract Arithmetic _Multiply(Arithmetic right); 
    protected abstract Arithmetic _Divide(Arithmetic right); 
    protected abstract Arithmetic _Negate(); 
    internal Arithmetic Add(Arithmetic right) { return _Add(right); } 
    internal Arithmetic Subtract(Arithmetic right) { return _Subtract(right); } 
    internal Arithmetic Multiply(Arithmetic right) { return _Multiply(right); } 
    internal Arithmetic Divide(Arithmetic right) { return _Divide(right); } 
    internal Arithmetic Negate() { return _Negate(); } 
    public abstract bool Equals(Arithmetic other); 
} 

public class RealNumberClass : Arithmetic 
{ 
    private readonly decimal value; 

    private RealNumberClass(decimal d) 
    { 
     this.value = d; 
    } 

    public static implicit operator decimal(RealNumberClass value) 
    { 
     return value.value; 
    } 

    public static implicit operator RealNumberClass(decimal value) 
    { 
     return new RealNumberClass(value); 
    } 

    public static RealNumberClass operator +(RealNumberClass left, RealNumberClass right) 
    { 
     return new RealNumberClass(left.value + right.value); 
    } 

    public static RealNumberClass operator -(RealNumberClass value) 
    { 
     return new RealNumberClass(-value.value); 
    } 

    public static RealNumberClass operator -(RealNumberClass left, RealNumberClass right) 
    { 
     return new RealNumberClass(left.value - right.value); 
    } 

    public static RealNumberClass operator *(RealNumberClass left, RealNumberClass right) 
    { 
     return new RealNumberClass(left.value * right.value); 
    } 

    public static RealNumberClass operator /(RealNumberClass left, RealNumberClass right) 
    { 
     return new RealNumberClass(left.value/right.value); 
    } 

    protected override Arithmetic _Add(Arithmetic right) 
    { 
     if (!(right is RealNumberClass)) 
      throw new ArgumentException(); 

     return this + (RealNumberClass)right; 
    } 

    protected override Arithmetic _Subtract(Arithmetic right) 
    { 
     if (!(right is RealNumberClass)) 
      throw new ArgumentException(); 

     return this - (RealNumberClass)right; 
    } 

    protected override Arithmetic _Multiply(Arithmetic right) 
    { 
     if (!(right is RealNumberClass)) 
      throw new ArgumentException(); 

     return this * (RealNumberClass)right; 
    } 

    protected override Arithmetic _Divide(Arithmetic right) 
    { 
     if (!(right is RealNumberClass)) 
      throw new ArgumentException(); 

     return this/(RealNumberClass)right; 
    } 

    protected override Arithmetic _Negate() 
    { 
     return -this; 
    } 

    public override bool Equals(Arithmetic other) 
    { 
     throw new NotImplementedException(); 
    } 
} 

Теперь, если я иду вперед и протестировать этот код со следующим кодом:

static void TestPerformance(int outerCount) 
    { 
     int count = 0; 

     do 
     { 
      var stopWatch = new Stopwatch(); 
      int repetitions = 100000; 
      testRealNumberStruct(1); 
      testRealNumberClass(1); 
      testDecimal(1); 
      double structAverage = 0, classAverage = 0, decimalAverage = 0; 

      for (int i = 0; i < outerCount; i++) 
      { 
       Console.WriteLine(); 
       stopWatch.Start(); 
       testRealNumberStruct(repetitions); 
       stopWatch.Stop(); 
       structAverage += stopWatch.ElapsedMilliseconds; 
       Console.WriteLine("RealNumber struct test: {0} ms", stopWatch.ElapsedMilliseconds); 

       stopWatch = new Stopwatch(); 
       stopWatch.Start(); 
       testRealNumberClass(repetitions); 
       stopWatch.Stop(); 
       classAverage += stopWatch.ElapsedMilliseconds; 
       Console.WriteLine("RealNumber class test: {0} ms", stopWatch.ElapsedMilliseconds); 
       stopWatch.Reset(); 

       stopWatch = new Stopwatch(); 
       stopWatch.Start(); 
       testDecimal(repetitions); 
       stopWatch.Stop(); 
       decimalAverage += stopWatch.ElapsedMilliseconds; 
       Console.WriteLine("Decimal test: {0} ms", stopWatch.ElapsedMilliseconds); 
       Console.WriteLine(); 
      } 

      Console.WriteLine(); 
      Console.WriteLine("Test #{0} results----------------------------------", ++count); 
      Console.WriteLine("RealNumber struct average: {0:F0} ms", structAverage/outerCount); 
      Console.WriteLine("RealNumber class average: {0:F0} ms", classAverage/outerCount); 
      Console.WriteLine("Decimal average: {0:F0} ms", decimalAverage/outerCount); 

     } while (Console.ReadKey().Key != ConsoleKey.Q); 
    } 

    private static void testRealNumberStruct(int repetitions) 
    { 
     for (int i = 0; i < repetitions; ++i) 
     { 
      IArithmetic d1 = (RealNumberStruct)1.25m; 
      IArithmetic d2 = (RealNumberStruct)(-0.25m); 

      var d = d1.Multiply(d2); 
      d = d.Add(d1); 
      d = d2.Divide(d); 
      d = d1.Subtract(d); 
     } 
    } 

    private static void testRealNumberClass(int repetitions) 
    { 
     for (int i = 0; i < repetitions; ++i) 
     { 
      Arithmetic d1 = (RealNumberClass)1.25m; 
      Arithmetic d2 = (RealNumberClass)(-0.25m); 
      var d = d1.Multiply(d2); 
      d = d.Add(d1); 
      d = d2.Divide(d); 
      d = d1.Subtract(d); 
     } 
    } 

    private static void testDecimal(int repetitions) 
    { 
     for (int i = 0; i < repetitions; ++i) 
     { 
      var d1 = 1.25m; 
      var d2 = -0.25m; 
      var d = d1 * d2; 
      d = d + d1; 
      d = d2/d; 
      d = d1 - d; 
     } 
    } 

Я постоянно получаю то, что я считаю странное поведение. Выход теста (outerCount = 3) состоит в следующем:

вещественное число тест структура: 40 мс

вещественное число испытаний Класс: 35 мс

Десятичный тест: 29 мс

вещественное число структура тест: 64 мс

Тест класса RealNumber: 32 мс

Десятичный тест: 27 мс

вещественное число тестов структура: 62 мс

вещественное число испытаний Класс: 33 мс

Десятичный тест: 27 мс

Тест # 1 results-- ---------------------------------

Общая стоимость RealNumber: 55 мс

вещественное число классов в среднем: 33 мс

Десятичный средний: 28 мс

Обратите внимание, что в первом запуске, производительность RealNumberStruct и RealNumberClass подобны (40 мс против 35 мс), но в прогонов 2 и 3 RealNumberStruct сбрасываются (62 и 52 мс), а рабочие характеристики RealNumberClass и decimal остаются постоянными.Это похоже на последовательное поведение, независимо от того, сколько я выполняю; первый запуск всегда значительно быстрее. Может кто-нибудь сказать мне, почему это происходит? Неужели это GC как-то мешает?

Тест выполняется в выпуске сборки вне отладчика. Может ли кто-нибудь еще воспроизвести это поведение?

EDIT: Исправлены некоторые опечатки в коде.

+1

Я не могу сказать точно, но просто взглянув на него: IArithmetic d1 = (RealNumberStruct) 1.25m; 'приводит к боксу, когда вы назначаете его локальному типу' IArithmetic', что может быть тем, что влияет на представление. Попытайтесь полностью избавиться от интерфейса на структуре. – vcsjones

+0

@vcsjones, но почему разница между первым прогоном и двумя другими внешними циклами? – InBetween

+1

Трудно сказать. Ваш код, как указано, не компилируется. – vcsjones

ответ

1

Вы не восстанавливаете Секундомер до RealNumber struct, поэтому последующие прогоны RealNumber struct включают время для предыдущего теста Decimal.

+1

LOL. я ненавижу себя. – InBetween

+0

@InBetween, самые простые ошибки - самые простые ошибки. :) –