我正在对一些类进行建模以表示 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/