tem*_*mpw 54 c# performance decimal
我正在用C#编写财务应用程序,其中性能(即速度)至关重要.因为它是一个财务应用程序,我必须集中使用Decimal数据类型.
在分析器的帮助下,我尽可能地优化了代码.在使用Decimal之前,所有操作都是使用Double数据类型完成的,并且速度提高了几倍.但是,Double不是一个选项,因为它具有二进制特性,在多个操作过程中会导致很多精度错误.
是否有可以与C#连接的十进制库,这可以使我在.NET中的原生Decimal数据类型上获得性能提升?
基于我已经得到的答案,我注意到我不够清楚,所以这里有一些额外的细节:
谢谢!
gbj*_*anb 41
您可以使用long数据类型.当然,你将无法在那里存储分数,但如果你将你的应用程序编码为存储便士而不是磅,你就可以了.对于长数据类型,准确度是100%,除非你使用大量数据(使用64位长类型),否则你会没事的.
如果你不能强制存储便士,那么在一个类中包装一个整数并使用它.
Jon*_*eet 22
你说它需要快速,但你有具体的速度要求吗?如果没有,你可以优化超越理智点:)
正如我刚坐在我旁边的朋友建议的那样,您可以升级硬件吗?这可能比重写代码便宜.
最明显的选择是使用整数而不是小数 - 其中一个"单位"就像"千分之一"(或者你想要的任何东西 - 你明白了).这是否可行将取决于您在十进制值上开始执行的操作.在处理这个问题时你需要非常小心 - 很容易犯错误(至少如果你像我一样).
分析器是否在您的应用程序中显示了可以单独优化的特定热点?例如,如果您需要在一小块代码中进行大量计算,则可以从十进制格式转换为整数格式,进行计算然后转换回来.这可以保持API的大部分代码的小数,这可能使它更容易维护.但是,如果您没有明显的热点,那可能不太可行.
+1分析并告诉我们速度是一个明确的要求,顺便说一句:)
这个问题已得到很好的讨论,但由于我正在挖掘这个问题一段时间,我想分享一些我的结果.
问题定义:已知十进制比双精度慢得多,但金融应用程序无法容忍在双精度计算时出现的任何假象.
研究
我的目的是测量存储浮点数的不同方法,并得出应该用于我们应用的结论.
如果我们可以使用它Int64
来存储具有固定精度的浮点数.10 ^ 6的乘数给了我们两个:足够的数字来存储分数,并且存储大范围以存储大量数据.当然,你必须小心这种方法(乘法和除法运算可能变得棘手),但我们已经准备好了,并且也想测量这种方法.除了可能的计算错误和溢出之外,您必须记住的一件事是,通常您不能将这些长数字暴露给公共API.所以所有内部计算都可以用long来执行,但在将数字发送给用户之前,他们应该转换为更友好的东西.
我已经实现了一个简单的原型类,它将一个长值包装到类似十进制的结构(称为它Money
)并将其添加到测量中.
public struct Money : IComparable
{
private readonly long _value;
public const long Multiplier = 1000000;
private const decimal ReverseMultiplier = 0.000001m;
public Money(long value)
{
_value = value;
}
public static explicit operator Money(decimal d)
{
return new Money(Decimal.ToInt64(d * Multiplier));
}
public static implicit operator decimal (Money m)
{
return m._value * ReverseMultiplier;
}
public static explicit operator Money(double d)
{
return new Money(Convert.ToInt64(d * Multiplier));
}
public static explicit operator double (Money m)
{
return Convert.ToDouble(m._value * ReverseMultiplier);
}
public static bool operator ==(Money m1, Money m2)
{
return m1._value == m2._value;
}
public static bool operator !=(Money m1, Money m2)
{
return m1._value != m2._value;
}
public static Money operator +(Money d1, Money d2)
{
return new Money(d1._value + d2._value);
}
public static Money operator -(Money d1, Money d2)
{
return new Money(d1._value - d2._value);
}
public static Money operator *(Money d1, Money d2)
{
return new Money(d1._value * d2._value / Multiplier);
}
public static Money operator /(Money d1, Money d2)
{
return new Money(d1._value / d2._value * Multiplier);
}
public static bool operator <(Money d1, Money d2)
{
return d1._value < d2._value;
}
public static bool operator <=(Money d1, Money d2)
{
return d1._value <= d2._value;
}
public static bool operator >(Money d1, Money d2)
{
return d1._value > d2._value;
}
public static bool operator >=(Money d1, Money d2)
{
return d1._value >= d2._value;
}
public override bool Equals(object o)
{
if (!(o is Money))
return false;
return this == (Money)o;
}
public override int GetHashCode()
{
return _value.GetHashCode();
}
public int CompareTo(object obj)
{
if (obj == null)
return 1;
if (!(obj is Money))
throw new ArgumentException("Cannot compare money.");
Money other = (Money)obj;
return _value.CompareTo(other._value);
}
public override string ToString()
{
return ((decimal) this).ToString(CultureInfo.InvariantCulture);
}
}
Run Code Online (Sandbox Code Playgroud)
实验
我测量了以下操作:加法,减法,乘法,除法,相等比较和相对(更大/更小)比较.我测量操作以下类型:double
,long
,decimal
和Money
.每次操作进行1.000.000次.所有数字都是在数组中预先分配的,因此在构造函数中调用自定义代码decimal
并且Money
不应该影响结果.
Added moneys in 5.445 ms
Added decimals in 26.23 ms
Added doubles in 2.3925 ms
Added longs in 1.6494 ms
Subtracted moneys in 5.6425 ms
Subtracted decimals in 31.5431 ms
Subtracted doubles in 1.7022 ms
Subtracted longs in 1.7008 ms
Multiplied moneys in 20.4474 ms
Multiplied decimals in 24.9457 ms
Multiplied doubles in 1.6997 ms
Multiplied longs in 1.699 ms
Divided moneys in 15.2841 ms
Divided decimals in 229.7391 ms
Divided doubles in 7.2264 ms
Divided longs in 8.6903 ms
Equility compared moneys in 5.3652 ms
Equility compared decimals in 29.003 ms
Equility compared doubles in 1.727 ms
Equility compared longs in 1.7547 ms
Relationally compared moneys in 9.0285 ms
Relationally compared decimals in 29.2716 ms
Relationally compared doubles in 1.7186 ms
Relationally compared longs in 1.7321 ms
Run Code Online (Sandbox Code Playgroud)
结论
decimal
比运算慢〜15倍于long
或double
; 分裂慢约30倍.Decimal
样的包装比的表现更好Decimal
,但仍比性能显著恶化double
和long
由于缺乏来自CLR的支持.Decimal
以绝对数量执行计算非常快:每秒40.000.000次操作.忠告
Decimal
由于CLR的支持不足,重新实施自己的结构没有多大意义.你可能会比它快,Decimal
但它永远不会那么快double
.Decimal
不足以满足您的应用需求,请考虑将计算切换为long
固定精度.在将结果返回给客户端之前,应将其转换为Decimal
.在我上一个答案四年后,我想根据我们多年来使用浮点数进行高性能计算的经验添加另一个答案。
\nDecimal
高性能计算上的数据类型存在两个主要问题:
虽然你对第一个问题无能为力,但第二个问题看起来更重要。当使用 64 位数字进行操作时,内存操作和处理器非常高效。128 位操作要繁重得多。因此 .NET 的实现Decimal
在设计上明显慢于操作Double
甚至对于读/写操作也是如此。
如果您的应用程序既需要浮点计算的准确性又需要此类操作的性能,那么 或 都不Double
适合Decimal
该任务。我们在我的公司(金融科技领域)采用的解决方案是使用Intel\xc2\xae 十进制浮点数学库之上的包装器。它实现了IEEE 754-2008 Decimal Floating-Point Arithmetic specification
提供 64 位浮点小数。
评论。Decimals
只能用于存储浮点数和对其进行简单的算术运算。所有繁重的数学(例如计算技术分析指标)都应该在Double
。
UPD 2020:我们开源了小数DFP库。它是双语的(C#
和java
)。有一些特殊之处需要java
记住,您不能在其中使用自定义非分配类型(结构)java
. 但这超出了本次讨论的范围。请随意使用。