C# Decimal datatype performance
我正在用C语言编写一个财务应用程序,其中性能(即速度)至关重要。因为它是一个金融应用程序,我必须集中使用十进制数据类型。
我已经在分析器的帮助下尽可能地优化了代码。在使用decimal之前,所有的操作都是用double数据类型完成的,速度快了几倍。但是,double不是一个选项,因为它的二进制性质,在多次操作过程中会导致很多精度错误。
是否有任何十进制库可以与C接口,使我的性能比.NET中的本机十进制数据类型有所提高?
根据我已经得到的答案,我注意到我不够清楚,下面是一些额外的细节:
- 应用程序必须尽可能快(也就是说,当使用double而不是decimal时,尽可能快地使用double将是一个梦想)。double比decimal快15倍,因为它是基于硬件的。
- 硬件已经是一流的(我在双氙气四核上运行),应用程序使用线程,所以CPU在机器上的利用率总是100%。此外,该应用程序在64位模式下运行,这使得它比32位具有可测量的性能优势。
- 我已经优化了超过健全点(超过一个半月的优化;信不信由你,现在大约需要1/5000的时间来做我最初作为参考的计算);这个优化涉及到所有事情:字符串处理、I/O、数据库访问和索引、内存、循环、改变某些事情的方式制造,甚至在任何地方都使用"切换"来"如果",都会产生不同。探查器现在清楚地显示剩余的性能罪魁祸首是decimal数据类型运算符。没有别的东西能增加相当长的时间。
- 你必须相信我:我已经尽可能地在C.NET领域优化了应用程序,我对它的当前性能感到非常惊讶。我现在正在寻找一个好主意,以提高十进制的性能,接近双倍。我知道这只是一个梦,但我只是想检查一下,我想所有可能的事情。:)
谢谢!
您可以使用long数据类型。当然,你不能在那里存储分数,但是如果你把你的应用程序编码成存储硬币而不是英镑,你会没事的。对于长数据类型,精度是100%,除非您使用的是大量的数字(使用64位长的类型),否则您会没事的。
如果您不能强制存储便士,那么将一个整数包装在一个类中并使用它。
你说速度要快,但是你有具体的速度要求吗?如果没有,你可能会很好地超越理智的极限。
正如坐在我旁边的一位朋友刚刚建议的,你能升级你的硬件吗?这可能比重写代码要便宜。
最明显的选择是使用整数而不是小数,其中一个"单位"类似于"千分之一美分"(或者你想要的任何东西),你就有了这个想法。这是否可行将取决于您对十进制值执行的操作。你在处理这件事时需要非常小心-很容易出错(至少如果你和我一样)。
探查器是否显示了应用程序中您可以单独优化的特定热点?例如,如果需要在一小段代码中进行大量计算,可以将十进制转换为整数格式,进行计算,然后再转换回整数格式。这样就可以保持API中大部分代码的小数位数,这很可能使维护更加容易。但是,如果没有明显的热点,这可能是不可行的。
+1用于分析并告诉我们速度是一个明确的要求,btw:)
问题基本上是硬件支持双精度/浮点型,而十进制等则不支持。也就是说,您必须在速度+有限精度和更高精度+较差性能之间进行选择。
这个问题得到了很好的讨论,但由于我已经研究了一段时间这个问题,我想分享我的一些结果。
问题定义:众所周知,小数比双精度慢得多,但金融应用程序不能容忍对双精度执行计算时出现的任何假象。
研究
我的目的是测量存储浮点指向数的不同方法,并得出一个结论,哪个方法应该用于我们的应用程序。
如果我们可以接受使用
我实现了一个简单的原型类,它将一个长值包装成类似十进制的结构(称为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | 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); } } |
实验
我测量了以下操作:加法、减法、乘法、除法、相等比较和相对(大/小)比较。我在测量以下类型的手术:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 |
结论
忠告
我不认为SSE2指令可以轻松地处理.NET十进制值。.NET DECIMAL数据类型为128位十进制浮点类型http://en.wikipedia.org/wiki/decimal128_浮点_格式,SSE2指令适用于128位整数类型。
老问题,不过还是很有道理的。
以下是一些支持使用long的数字。
执行1000000次添加所需的时间
1 2 3 | Long 231 mS Double 286 mS Decimal 2010 mS |
简言之,十进制比长的或两倍的慢约10倍。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 |
MMX/SSE/SSE2怎么样?
我想这会有帮助…所以…decimal是128位数据类型,sse2也是128位…它可以在1个CPU时钟周期内添加,sub,div,mul十进制数…
可以使用VC++为SSE2编写DLL,然后在应用程序中使用该DLL
例如//你可以这样做
VC++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include #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.*
1 2 3 4 5 6 7 8 9 10 11 12 | [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); } |
我还不能给出评论或投票否决,因为我刚开始堆栈溢出。我对Alexsmart(发表于2008年12月23日12:31)的评论是,表达式round(n/precision,precision),其中n是int,precision是long,不会做他认为的事情:
1)n/precision将返回一个整数除法,即它已经被四舍五入,但您将无法使用任何小数。舍入行为也不同于math.round(…)。
2)由于math.round(double,int)和math.round(decimal,int)之间存在歧义,代码"return math.round(n/precision,precision).toString()"无法编译。您将不得不强制转换为十进制(不是双精度的,因为它是一个金融应用程序),因此也可以首先使用十进制。
3)n/精度,精度为4时,不会截断为四位小数,而是除以4。例如,math.round((decimal)(1234567/4),4)返回308641。(1234567/4=308641.75),而您可能想要得到的是1235000(从尾随的567四舍五入到4位精度)。注意,math.round允许四舍五入到一个固定的点,而不是一个固定的精度。
更新:我现在可以添加评论,但没有足够的空间将此评论放入评论区域。
用双倍硬币储存"便士"。除了解析输入和打印输出之外,您还具有与您测量的速度相同的速度。您克服了64位整数的限制。您有一个不截断的除法。注意:如何使用除法后的双精度结果取决于您。在我看来,这是满足您需求的最简单方法。