我正在用C#编写财务应用程序,其中性能(即速度)至关重要。因为它是一个财务应用程序,我必须集中使用Decimal数据类型。
我在探查器的帮助下尽可能地优化了代码。在使用Decimal之前,所有操作都是使用Double数据类型完成的,并且速度提高了几倍。但是,Double不是一个选项,因为它具有二进制特性,在多个操作过程中会导致很多精度错误。
是否有可以与C#连接的十进制库可以让我比.NET中的原生Decimal数据类型提高性能?
根据我已经得到的答案,我注意到我不够清楚,所以这里有一些额外的细节:
谢谢!
答案 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);
}
}
<强>实验强>
我测量了以下操作:加法,减法,乘法,除法,相等比较和相对(更大/更小)比较。我正在测量以下类型的操作: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
<强>结论强>
decimal
上的加法,减法,乘法,比较运算比long
或double
上的运算慢约15倍;分裂慢了约30倍。Decimal
相比,Decimal
的效果优于double
的效果,但由于缺乏CLR的支持,性能仍然明显低于long
和Decimal
的效果。< / LI>
Decimal
执行计算非常快:每秒40.000.000次操作。<强>建议强>
Decimal
没有多大意义。您可能会比double
更快,但它永远不会像Decimal
那么快。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
数据类型存在两个主要问题:
虽然您在第一个问题上不能做很多事情,但第二个看起来就更重要了。使用64位数字进行操作时,内存操作和处理器非常高效。 128位操作要重得多。因此,Decimal
的.NET实现在设计上比Double
上的操作要慢得多,即使对于读/写操作也是如此。
如果您的应用程序既需要浮点计算的精度又需要此类操作的性能,那么Double
或Decimal
都不适合该任务。我们在公司(Fintech域)中采用的解决方案是在Intel® Decimal Floating-Point Math Library上使用包装器。它实现了IEEE 754-2008 Decimal Floating-Point Arithmetic specification
,提供了64位浮点小数。
备注。 Decimals
仅应用于存储浮点数和对其进行简单的算术运算。所有繁重的数学运算,例如计算用于技术分析的指标,都应在Double
值上进行。