C#十进制数据类型性能

tem*_*mpw 54 c# performance decimal

我正在用C#编写财务应用程序,其中性能(即速度)至关重要.因为它是一个财务应用程序,我必须集中使用Decimal数据类型.

在分析器的帮助下,我尽可能地优化了代码.在使用Decimal之前,所有操作都是使用Double数据类型完成的,并且速度提高了几倍.但是,Double不是一个选项,因为它具有二进制特性,在多个操作过程中会导致很多精度错误.

是否有可以与C#连接的十进制库,这可以使我在.NET中的原生Decimal数据类型上获得性能提升?

基于我已经得到的答案,我注意到我不够清楚,所以这里有一些额外的细节:

  • 应用程序必须尽可能快(即使用Double而不是Decimal时的速度,这将是一个梦想).Double比Decimal快约15倍,因为操作是基于硬件的.
  • 硬件已经是一流的(我在双氙四核上运行)并且应用程序使用线程,因此机器上的CPU利用率始终是100%.此外,该应用程序以64位模式运行,与32位相比,它具有可测量的性能优势.
  • 我已经超越了理智的程度(超过一个半月的优化;信不信由你,它现在需要大约1/5000的时间来完成我最初用作参考的相同计算); 这种优化涉及到一切:字符串处理,I/O,数据库访问和索引,内存,循环,改变一些事情的方式,甚至使用"切换"而不是"if"到处都有所不同.分析器现在清楚地显示剩余的性能元凶是在Decimal数据类型运算符上.没有别的东西会增加相当多的时间.
  • 你必须在这里相信我:我已经尽可能地进入C#.NET领域来优化应用程序,我对它目前的性能感到非常惊讶.我现在正在寻找一个好主意,以便将Decimal性能提高到接近Double的水平.我知道这只是一个梦想,但只是想检查一下,我想到了一切可能.:)

谢谢!

gbj*_*anb 41

您可以使用long数据类型.当然,你将无法在那里存储分数,但如果你将你的应用程序编码为存储便士而不是磅,你就可以了.对于长数据类型,准确度是100%,除非你使用大量数据(使用64位长类型),否则你会没事的.

如果你不能强制存储便士,那么在一个类中包装一个整数并使用它.

  • @Vilx:Ngen不会加速一个程序.Ngen所做的就是加快程序启动的速度. (4认同)
  • 这实际上是十进制类型的作用,除了缩放因子是可变的,类型使用96位表示数字(总共128位); 这就是为什么它比使用long(64位)的建议版本慢.(这些评论字段太短了.) (3认同)
  • 使用long可能对现有逻辑非常困难.简单地说"存储便士"可能适用于简单的会计,但不一定适用于复利计算和其他非整数公式.谁知道他正在做什么样的计算? (2认同)
  • 请记住,使用整数类型时,将发生整数除法,并且值将被截断而不是舍入. (2认同)

Jon*_*eet 22

你说它需要快速,但你有具体的速度要求吗?如果没有,你可以优化超越理智点:)

正如我刚坐在我旁边的朋友建议的那样,您可以升级硬件吗?这可能比重写代码便宜.

最明显的选择是使用整数而不是小数 - 其中一个"单位"就像"千分之一"(或者你想要的任何东西 - 你明白了).这是否可行将取决于您在十进制值上开始执行的操作.在处理这个问题时你需要非常小心 - 很容易犯错误(至少如果你像我一样).

分析器是否在您的应用程序中显示了可以单独优化的特定热点?例如,如果您需要在一小块代码中进行大量计算,则可以从十进制格式转换为整数格式,进行计算然后转换回来.这可以保持API的大部分代码的小数,这可能使它更容易维护.但是,如果您没有明显的热点,那可能不太可行.

+1分析并告诉我们速度是一个明确的要求,顺便说一句:)


Bri*_*sen 8

问题基本上是硬件支持double/float,而Decimal等则不支持.即你必须在速度+有限精度和更高精度+更差性能之间做出选择.


use*_*819 8

这个问题已得到很好的讨论,但由于我正在挖掘这个问题一段时间,我想分享一些我的结果.

问题定义:已知十进制比双精度慢得多,但金融应用程序无法容忍在双精度计算时出现的任何假象.

研究

我的目的是测量存储浮点数的不同方法,并得出应该用于我们应用的结论.

如果我们可以使用它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,decimalMoney.每次操作进行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)

结论

  1. 加法,减法,乘法,比较操作上decimal比运算慢〜15倍于longdouble; 分裂慢约30倍.
  2. 性能Decimal样的包装比的表现更好Decimal,但仍比性能显著恶化doublelong由于缺乏来自CLR的支持.
  3. Decimal以绝对数量执行计算非常快:每秒40.000.000次操作.

忠告

  1. 除非您有非常繁重的计算案例,否则请使用小数.相对数字,它们比长和慢两倍,但绝对数字看起来不错.
  2. Decimal由于CLR的支持不足,重新实施自己的结构没有多大意义.你可能会比它快,Decimal但它永远不会那么快double.
  3. 如果性能Decimal不足以满足您的应用需求,请考虑将计算切换为long固定精度.在将结果返回给客户端之前,应将其转换为Decimal.


use*_*819 6

在我上一个答案四年后,我想根据我们多年来使用浮点数进行高性能计算的经验添加另一个答案。

\n

Decimal高性能计算上的数据类型存在两个主要问题:

\n
    \n
  1. CLR 将此类型视为常规结构(对于其他内置类型没有特殊支持)
  2. \n
  3. 是128位的
  4. \n
\n

虽然你对第一个问题无能为力,但第二个问题看起来更重要。当使用 64 位数字进行操作时,内存操作和处理器非常高效。128 位操作要繁重得多。因此 .NET 的实现Decimal在设计上明显慢于操作Double甚至对于读/写操作也是如此。

\n

如果您的应用程序既需要浮点计算的准确性又需要此类操作的性能,那么 或 都不Double适合Decimal该任务。我们在我的公司(金融科技领域)采用的解决方案是使用Intel\xc2\xae 十进制浮点数学库之上的包装器。它实现了IEEE 754-2008 Decimal Floating-Point Arithmetic specification提供 64 位浮点小数。

\n

评论。Decimals只能用于存储浮点数和对其进行简单的算术运算。所有繁重的数学(例如计算技术分析指标)都应该在Double

\n

UPD 2020:我们开源了小数DFP库。它是双语的(C#java)。有一些特殊之处需要java记住,您不能在其中使用自定义非分配类型(结构)java. 但这超出了本次讨论的范围。请随意使用。

\n