多态性能命中

时间:2013-06-04 14:56:18

标签: c# performance generics polymorphism

我正在建模一些类来表示C#中的度量单位。例如,我有毫米和英寸模型,IDistanceUnit接口和基础DistanceUnit类提供了常见的实现细节。

在每个具体类中都有基本的算术函数,如此定义:

public class Inches : 
    DistanceUnit<Inches>, 
    IDistanceUnit, 
    INumericUnit<Inches, IDistanceUnit>
{
    // ... snip ...
    public Inches Add(IDistanceUnit unit)
    {
        return new Inches(Value + unit.ToInches().Value);
    }
    // ... snip ...
}

对转换为英寸和毫米的10,000,000个不同值的基准进行基准测试,其但可接受的性能损失很小。手动执行转换的原始双打大约需要70-100毫秒,其中类需要大约700-1000毫秒。

我可以将细节抽象到DistanceUnit基类中,并删除一些不必要的重复:

public abstract class DistanceUnit<TConcrete>
        IDistanceUnit,
        INumericUnit<TConcrete, IDistanceUnit>
    where TConcrete :
        IDistanceUnit,
        INumericUnit<TConcrete, IDistanceUnit>,
        new()
{
    // ... snip ...
    public TConcrete Add(IDistanceUnit unit)
    {
        TConcrete result = new TConcrete();
        reuslt.Value = Value + ToThis(unit).Value();
        return result;
    } 
    // ... snip ...
}
// ToThis() calls the correct "ToXYZ()" for the current implementing class

这会使我的表现降低至少10倍。我看到大约8000毫秒,只是将实现移动到基类,相比之下,800毫秒。

我已经排除了手动测试的一些内容:

  • 切换到使用ToThis(IDistanceUnit)显示没有明显的效果 在具体类而不是直接使用时会遇到性能损失 致电“ToInches”或“ToMillimeters”
  • 使用无参数构造函数(new TConcrete())创建对象并进行分配 该值后来最差的是几毫秒的性能超过10,000,000 计算。

我正在使用以下代码对我的结果进行基准测试:

int size = 10000000;
double[] mms = new double[size];
double[] inches = new double[size];

// Fill in some arbitrary test values
for (int i = 0; i < size; i++)
{
    mms[i] = i;
    inches[i] = i;
}

Benchmark("Manual Conversion", () =>
{
    for (int i = 0; i < size; i++)
    {
        var result = mms[i] + (inches[i] * 25.4);
    }
});

Benchmark("Unit Classes", () => 
{
    for (int i = 0; i < size; i++)
    {
        var result = (new Millimeters(mms[i])) + (new Inches(inches[i]));
    }
}

Benchmark只是在提供的Action周围调用一个秒表,并打印出以毫秒为单位的时间。

唯一似乎导致重大差异的是Add函数从具体类到抽象基类的移动,但我不明白为什么我会有近10倍的性能下降就是这样。 任何人都可以向我解释(如果可能的话,建议一种在不诉诸代码重复的情况下获得更好速度的方法)?

1 个答案:

答案 0 :(得分:3)

对于我在评论中所说的第一个性能,我需要了解一些实现细节: ToInches()方法,ToThis()方法,Value属性的类型或代码。

对于第二次性能命中,最可能的原因是你对基类使用new()约束并使用
TConcrete result = new TConcrete();

编译器在这种情况下生成Activator.CreateInstance<TConcrete>(),此方法使用反射并且速度很慢。如果您有数百万个实例,它肯定会降低性能。 Here你可以找到Jon Skeet的一些基准。

如果使用&gt; = 3.5框架,则可以使用表达式树在基类中构建对象的快速通用实例化:

private static Func<TConcrete> _createFunc;

    private static TConcrete CreateNew()
    {
            if (_func == null)
            {
                _createFunc = Expression.Lambda<Func<TConcrete>>(Expression.New(typeof (TConcrete))).Compile();
            }
            return _createFunc.Invoke();
    }

    public TConcrete Add(IDistanceUnit unit)
    {
            TConcrete result = CreateNew();
            result.Value = Value + ToThis(unit).Value();
            return result;
    }