c# - 多态性能受到影响

标签 c# performance generics polymorphism

我正在对一些类进行建模以表示 C# 中的度量单位。例如,我对毫米和英寸进行了建模,并使用 IDistanceUnit 接口(interface)和提供通用实现细节的 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 次不同值的添加进行基准测试会产生较小的性能影响,但可以接受。手动执行转换的原始 double 大约需要 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 倍。 任何人都可以向我解释一下(如果可能的话,建议一种无需重复代码即可获得更好速度的方法)?

最佳答案

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

对于第二个性能影响,最可能的原因是您对基类使用 new() 约束并使用
TConcrete result = new TConcrete();

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

如果您使用 >= 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;
    } 

关于c# - 多态性能受到影响,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16921053/

相关文章:

generics - Swift 要求两个泛型是同一类型

c# - 组合派生类的接口(interface)

c# - Shell32.Folder.GetDetailsOf(..,..) 有哪些可用选项?

c# - 替换两个的通用方法

带有条件语句的 C# 泛型类型参数

arrays - 为什么快速排序平均比其他排序快?

c# 通过 LDAP 针对 Active Directory

c# - 尝试访问派生类中基类属性的 protected 访问器时出错

c++ - 为什么双重优先于 float ?

java - Spark 2.2.0 : How to remove Specific Duplicates from a Dataset of a list column