В продолжении вопроса я отправил вчера, Mathematics and generics я решил пойти дальше и создать простой RealNumber
класс оберточной decimal
для того, чтобы общей математики и сделать несколько простых тестов, чтобы увидеть производительность по сравнению (пожалуйста не комментировать почему, зачем или как реализовать обертку для десятичной, это не цель этого вопроса).Weird тест производительность поведение
Я решил сравнить следующие 2 реализации:
RealNumberStruct
: интерфейс варианта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: Исправлены некоторые опечатки в коде.
Я не могу сказать точно, но просто взглянув на него: IArithmetic d1 = (RealNumberStruct) 1.25m; 'приводит к боксу, когда вы назначаете его локальному типу' IArithmetic', что может быть тем, что влияет на представление. Попытайтесь полностью избавиться от интерфейса на структуре. – vcsjones
@vcsjones, но почему разница между первым прогоном и двумя другими внешними циклами? – InBetween
Трудно сказать. Ваш код, как указано, не компилируется. – vcsjones