我正在建模一些类来表示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倍的性能下降就是这样。 任何人都可以向我解释(如果可能的话,建议一种在不诉诸代码重复的情况下获得更好速度的方法)?
答案 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;
}