C#十进制数据types的性能

我正在写C#的财务应用程序,其中性能(即速度)是至关重要的。 因为这是一个金融应用程序,我不得不集中使用Decimal数据types。

我已经尽可能在代码分析器的帮助下优化了代码。 在使用Decimal之前,所有事情都是使用Double数据types完成的,速度要快几倍。 然而,Double不是一个选项,因为它的二进制性质,在多个操作过程中会导致很多精确的错误。

有没有任何十进制库,我可以与C#接口,可以给我一个性能改善超过原生的十进制数据types在.NET中?

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

  • 应用程序必须尽可能快(即使用Double而不是Decimal就是梦想)。 Double的速度比Decimal快大约15倍,因为操作是基于硬件的。
  • 硬件已经是一stream的(我在双氙气四核上运行),并且应用程序使用线程,所以CPU使用率始终是100%。 此外,该应用程序运行在64位模式,这使得它具有超过32位的测量性能优势。
  • 我已经优化了过去的理智点(一个半月以上的优化;不pipe信不信,现在大概需要做相同的计算所花费的约1/5000)。 这个优化涉及到一切:string处理,I / O,数据库访问和索引,内存,循环,改变某些事情的方式,甚至在所有地方都使用“切换”。 分析器现在清楚地显示了其余的性能原因在于十进制数据types运算符。 没有别的东西加起来相当长的时间。
  • 你必须在这里相信我:我已经尽可能地去C#.NET的领域来优化应用程序,我对它当前的性能感到非常惊讶。 我现在正在寻找一个好主意,以提高十进制性能接近双。 我知道这只是一个梦,但只是想检查我想到的一切可能。 🙂

谢谢!

您可以使用长数据types。 当然,你不能在那里存储分数,但是如果你编码你的应用程序存储便士而不是磅,那么你就没事了。 对于长数据types,精度是100%,除非你使用大数字(使用64位长types),否则你会没事的。

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

你说要快,但是你有具体的速度要求吗? 如果没有,你可能会优化过去的智慧点:)

正如我坐在旁边的一个朋友刚才所build议的那样,你能升级你的硬件吗? 这可能比重写代码便宜。

最明显的select是使用整数而不是小数 – 其中一个“单位”是“千分之一分”(或任何你想要的 – 你明白了)。 无论是否可行,都将取决于您在十进制值上执行的操作。 处理时需要非常小心 – 犯错误很容易(至less如果你像我一样)。

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

+1分析和告诉我们,速度是一个明确的要求,btw 🙂

问题基本上是硬件支持double / float,而Decimal等不是。 也就是说,您必须在速度+有限精度和更高精度+更差性能之间进行select。

MMX / SSE / SSE2呢?

我认为这将有助于…所以…十进制是128位数据types和SSE2是128位太…它可以添加,1个CPU的tick,sub,div,mul小数…

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

例如//你可以做这样的事情

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); } 

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

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

这里有一些数字来支持使用Long的想法。

所需时间为100,000,000增加

 Long 231 mS Double 286 mS Decimal 2010 mS 

简而言之,十进制是〜10倍慢或长。

码:

 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 

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

问题定义:小数已知比双打慢得多,但金融应用程序不能容忍在双打计算时出现的任何人为因素。

研究

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

如果我们可以接受使用Int64来存储具有固定精度的浮点数。 10 ^ 6的倍数给了我们两个:足够的数字来存储分数,而且还有一个大的范围来存储大量数据。 当然,这种方法必须要小心(乘法和除法操作可能会变得棘手),但是我们已经准备好了,并且也想要测量这种方法。 有一件事你必须记住,除了可能的计算错误和溢出,通常你不能公开这些长的数字公共API。 所以所有的内部计算都可以用很长的时间来完成,但是在把数字发送给用户之前,他们应该被转换成更友好的东西。

我已经实现了一个简单的原型类,它将一个长整型值包装成一个十进制结构(称为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); } } 

实验

我测量了以下操作:加法,减法,乘法,除法,相等比较和相对(大/小)比较。 我正在测量以下types的操作: doublelongdecimalMoney 。 每个操作进行了1000,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 like包装的性能要好于Decimal性能,但由于CLR缺乏支持,性能仍然差于doublelong性能。
  3. 在绝对数字上执行Decimal计算是相当快的:每秒40.000.000次操作。

忠告

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

自从我刚刚开始堆栈溢出时,我无法发表评论或投票。 我对alexsmart(张贴2008年12月23日12:31)的评论是,expression轮(n /精度,精度),其中n是int和精度很高,不会做他认为:

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

2)由于Math.Round(double,int)和Math.Round(decimal,int)之间的歧义,代码“ return Math.Round(n / precision,precision).ToString() ”不能编译。 你将不得不投入十进制(因为它是一个金融应用程序不是双倍的),因此也可以用小数首先。

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

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

存储“便士”使用双。 除了parsinginput和打印输出外,您还可以测量相同的速度。 你克服了64位整数的限制。 你有一个部门不截断。 注意:由你决定如何使用分割后的双重结果。 这在我看来是最简单的方法来满足您的要求。