怪异的性能测试行为

本文关键字:性能测试 | 更新日期: 2023-09-27 18:22:14

作为我昨天发布的问题的后续,数学和泛型,我决定继续编写一个简单的RealNumber类包装decimal,以启用泛型数学,并进行一些简单的测试来比较性能(请不要评论为什么、为什么或如何实现十进制包装,这不是这个问题的目的)。

我决定比较以下两种实现:

  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)的输出如下:

RealNumber结构测试:40ms

RealNumber类测试:35毫秒

十进制测试:29毫秒

RealNumber结构测试:64毫秒

RealNumber类测试:32毫秒

十进制测试:27ms

RealNumber结构测试:62毫秒

RealNumber类测试:33毫秒

十进制测试:27ms

测试#1结果------------------------------

RealNumber结构平均值:55毫秒

RealNumber类平均值:33毫秒

十进制平均值:28毫秒

注意,在第一次运行中,RealNumberStructRealNumberClass的性能相似(40ms vs 35ms),但在第2次和第3次运行中RealNumberStruct下降(62和52ms),而RealNumberClassdecimal的性能保持不变。无论我跑了多少次,这似乎都是一种始终如一的行为;第一次跑步总是快得多。有人能告诉我为什么会发生这种事吗?是GC挡道了吗?

测试在调试器外部的发布版本中运行。其他人能重现这种行为吗?

编辑:修复了代码中的一些拼写错误。

怪异的性能测试行为

您没有在RealNumber struct之前重建Stopwatch,因此RealNumber struct的后续运行包括先前Decimal测试的时间。