C#十进制数据类型性能

时间:2008-12-14 19:08:33

标签: 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的水平。我知道这只是一个梦想,但只是想检查一下,我想到了一切可能。 :)

谢谢!

10 个答案:

答案 0 :(得分:40)

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

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

答案 1 :(得分:22)

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

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

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

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

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

答案 2 :(得分:8)

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

答案 3 :(得分:6)

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

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

<强>研究

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

如果我们可以使用Int64来存储具有固定精度的浮点数。 10 ^ 6的乘数给了我们两个:足够的数字来存储分数,并且存储大范围以存储大量数据。当然,你必须小心这种方法(乘法和除法运算可能变得棘手),但我们已经准备好了,并且也想测量这种方法。除了可能的计算错误和溢出之外,您必须记住的一件事是,通常您不能将这些长数字暴露给公共API。因此,所有内部计算都可以使用long进行,但在将数字发送给用户之前,应将它们转换为更友好的内容。

我实现了一个简单的原型类,它将一个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);
    }
}

<强>实验

我测量了以下操作:加法,减法,乘法,除法,相等比较和相对(更大/更小)比较。我正在测量以下类型的操作:doublelongdecimalMoney。每次操作进行1.000.000次。所有数字都是在数组中预先分配的,因此在decimalMoney的构造函数中调用自定义代码不应影响结果。

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

<强>结论

  1. decimal上的加法,减法,乘法,比较运算比longdouble上的运算慢约15倍;分裂慢了约30倍。
  2. Decimal相比,Decimal的效果优于double的效果,但由于缺乏CLR的支持,性能仍然明显低于longDecimal的效果。< / LI>
  3. 以绝对数字对Decimal执行计算非常快:每秒40.000.000次操作。
  4. <强>建议

    1. 除非您有非常繁重的计算案例,否则请使用小数。相对数字,它们比长和慢两倍,但绝对数字看起来不错。
    2. 由于CLR的支持不足,使用您自己的结构重新实施Decimal没有多大意义。您可能会比double更快,但它永远不会像Decimal那么快。
    3. 如果long的性能不足以满足您的应用需求,请考虑以固定精度将计算结果切换为Decimal。在将结果返回给客户端之前,应将其转换为Collecting pystashop Downloading pystashop-0.4.tar.gz Complete output from command python setup.py egg_info: Traceback (most recent call last): File "<string>", line 20, in <module> File "C:\Users\Me\AppData\Local\Temp\pip-build-zyt3yyca\pystashop\setup.py", line 12, in <module> execfile(os.path.join('pystashop', 'version.py')) NameError: name 'execfile' is not defined ---------------------------------------- Command "python setup.py egg_info" failed with error code 1 in C:\Users\Me\AppData\Local\Temp\pip-build-zyt3yyca\pystashop

答案 4 :(得分:3)

我不认为SSE2指令可以轻松使用.NET Decimal值。 .NET十进制数据类型为 128位十进制浮点类型http://en.wikipedia.org/wiki/Decimal128_floating-point_format,SSE2指令适用于 128位整数类型

答案 5 :(得分:2)

MMX / SSE / SSE2怎么样?

我认为这会有所帮助...... 所以... 十进制是128位数据类型,SSE2也是128位...它可以在1个CPU滴答中添加,sub,div,mul十进制......

您可以使用VC ++为SSE2编写DLL,然后在您的应用程序中使用该DLL

e.g //你可以做这样的事情

<强> VC ++

#include <emmintrin.h>
#include <tmmintrin.h>

extern "C" DllExport __int32* sse2_add(__int32* arr1, __int32* arr2);

extern "C" DllExport __int32* sse2_add(__int32* arr1, __int32* arr2)
{
    __m128i mi1 = _mm_setr_epi32(arr1[0], arr1[1], arr1[2], arr1[3]);
    __m128i mi2 = _mm_setr_epi32(arr2[0], arr2[1], arr2[2], arr2[3]);

    __m128i mi3 = _mm_add_epi32(mi1, mi2);
    __int32 rarr[4] = { mi3.m128i_i32[0], mi3.m128i_i32[1], mi3.m128i_i32[2], mi3.m128i_i32[3] };
    return rarr;
}

<强> C#

[DllImport("sse2.dll")]
private unsafe static extern int[] sse2_add(int[] arr1, int[] arr2);

public unsafe static decimal addDec(decimal d1, decimal d2)
{
    int[] arr1 = decimal.GetBits(d1);
    int[] arr2 = decimal.GetBits(d2);

    int[] resultArr = sse2_add(arr1, arr2);

    return new decimal(resultArr);
}

答案 6 :(得分:2)

老问题,但仍然非常有效。

以下是一些支持使用Long的想法的数字。

执行100'000'000次添加所花费的时间

Long     231 mS
Double   286 mS
Decimal 2010 mS

简而言之,十进制或双倍的结果要小十倍。

代码:

Sub Main()
    Const TESTS = 100000000
    Dim sw As Stopwatch

    Dim l As Long = 0
    Dim a As Long = 123456
    sw = Stopwatch.StartNew()
    For x As Integer = 1 To TESTS
        l += a
    Next
    Console.WriteLine(String.Format("Long    {0} mS", sw.ElapsedMilliseconds))

    Dim d As Double = 0
    Dim b As Double = 123456
    sw = Stopwatch.StartNew()
    For x As Integer = 1 To TESTS
        d += b
    Next
    Console.WriteLine(String.Format("Double  {0} mS", sw.ElapsedMilliseconds))

    Dim m As Decimal = 0
    Dim c As Decimal = 123456
    sw = Stopwatch.StartNew()
    For x As Integer = 1 To TESTS
        m += c
    Next
    Console.WriteLine(String.Format("Decimal {0} mS", sw.ElapsedMilliseconds))

    Console.WriteLine("Press a key")
    Console.ReadKey()
End Sub

答案 7 :(得分:1)

由于我刚开始堆栈溢出,我无法发表评论或拒绝投票。我对alexsmart的评论(发表于2008年12月23日12:31)是圆形表达式(n /精度,精度),其中n是int,精度很长,不会做他想的那样:

1)n / precision将返回一个整数除法,即它已经被舍入但你将无法使用任何小数。舍入行为也与Math.Round(...)不同。

2)代码“​​返回Math.Round(n / precision,precision).ToString()”由于Math.Round(double,int)和Math之间的歧义而无法编译。 Round(十进制,int)。您将不得不转换为十进制(因为它是一个财务应用程序而不是双倍),因此可以首先使用十进制。

3)n / precision,其中精度为4,不会截断为四位小数,而是除以4.例如, Math.Round((十进制)(1234567/4),4)返回308641 。(1234567/4 = 308641.75),而你可能想要的是得到1235000(从尾随的567四舍五入到4位数的精度)。请注意,Math.Round允许舍入到固定点,而不是固定精度。

更新:我现在可以添加评论,但没有足够的空间将这个放入评论区域。

答案 8 :(得分:0)

商店&#34;便士&#34;使用双。除了解析输入和打印输出之外,您还可以测量相同的速度。你克服了64位整数的限制。你有一个不截断的分裂。注意:取决于如何在分割后使用双重结果。在我看来,这是满足您要求的最简单方法。

答案 9 :(得分:0)

在我的previous answer之后的4年,我想根据我们多年来在使用浮点数进行高性能计算方面的经验来补充另一篇。

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

  1. CLR将此类型视为常规结构(对其他内置类型没有特殊支持)
  2. 输入为128位

虽然您在第一个问题上不能做很多事情,但第二个看起来就更重要了。使用64位数字进行操作时,内存操作和处理器非常高效。 128位操作要重得多。因此,Decimal的.NET实现在设计上比Double上的操作要慢得多,即使对于读/写操作也是如此。

如果您的应用程序既需要浮点计算的精度又需要此类操作的性能,那么DoubleDecimal都不适合该任务。我们在公司(Fintech域)中采用的解决方案是在Intel® Decimal Floating-Point Math Library上使用包装器。它实现了IEEE 754-2008 Decimal Floating-Point Arithmetic specification,提供了64位浮点小数。

备注。 Decimals仅应用于存储浮点数和对其进行简单的算术运算。所有繁重的数学运算,例如计算用于技术分析的指标,都应在Double值上进行。