我正在建模一些类来表示 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 倍。与 800 毫秒相比,我看到大约 8000 毫秒,只是将实现移动到基类中。
我已经从手动测试中排除了一些事情:
ToThis(IDistanceUnit)
当在具体类中使用而不是直接调用“ToInches”或“ToMillimeters”时,切换到 using没有显示出明显的性能影响- 使用无参数构造函数
new TConcrete()
(
我正在使用以下代码对我的结果进行基准测试:
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 倍的性能下降。任何人都可以向我解释这一点(如果可能的话,建议一种方法来获得更好的速度而不诉诸代码重复)?