Reputation: 699
I'm writing a financial application in C# where performance (i.e. speed) is critical. Because it's a financial app I have to use the Decimal datatype intensively.
I've optimized the code as much as I could with the help of a profiler. Before using Decimal, everything was done with the Double datatype and the speed was several times faster. However, Double is not an option because of its binary nature, causing a lot of precision errors over the course of multiple operations.
Is there any decimal library that I can interface with C# that could give me a performance improvement over the native Decimal datatype in .NET?
Based on the answers I already got, I noticed I was not clear enough, so here are some additional details:
Thanks!
Upvotes: 69
Views: 29040
Reputation: 3640
4 years after my previous answer I would like to add another one based on the experience we had over the years on working with high-performance computations with floating-point numbers.
There are two major problems with Decimal
data type on high-performance computations:
While you cannot do much about the first issue, second looks even more important. Memory operations and processors are extremely efficient when operating with 64-bit numbers. 128-bit operations are much heavier. Thus .NET implementation of Decimal
is by design significantly slower that the operation on Double
even for read/write operations.
If your application needs both the accuracy of floating-point computations and performance of such operations then neither Double
or Decimal
are suitable for the task. The solution that we have adopted in my company (Fintech domain) is to use a wrapper on top of Intel® Decimal Floating-Point Math Library. It implements the IEEE 754-2008 Decimal Floating-Point Arithmetic specification
providing 64-bit floating-point decimals.
Remarks. Decimals
should only be used for storing the floating-point numbers and simple arithmetic operations on them. All heavy mathematics like calculating indicators for technical analysis should be performed on Double
values.
UPD 2020: We have open-sourced the library for decimals DFP. It is bi-lingual (C#
and java
). There are some peculiarities for java
keeping in mind that you cannot have custom non-allocating types (structures) in java
. But this is out of scope in terms of this discussion. Feel free to use.
Upvotes: 7
Reputation: 3640
The question is well discussed but since I was digging this problem for a while I would like to share some of my results.
Problem definition: Decimals are known to be much slower than doubles but financial applications cannot tolerate any artefacts that arise when calculations are performed on doubles.
Research
My aim was to measure different approaches of storing float-pointing numbers and to make a conclusion which one should be used for our application.
If was acceptable for us to use Int64
to store floating point numbers with fixed precision. Multiplier of 10^6 was giving us both: enough digits to store fractions and stil a big range to store large amounts. Of course, you have to be careful whith this approach (multiplication and division operations might become tricky), but we were ready and wanted to measure this approach as well. One thing you have to keep in mind except for possible calculation errors and overflows, is that usually you cannot expose those long numbers to public API. So all internal calculations could be performed with longs but before sending the numbers to the user they should be converted to something more friendly.
I've implemented a simple prototype class that wraps a long value to a decimal-like structure (called it Money
) and added it to the measurments.
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);
}
}
Experiment
I measured following operations: addition, subtraction, multiplication, division, equality comparison and relative (greater/less) comparison. I was measuring operations on the following types: double
, long
, decimal
and Money
. Each operation was performed 1.000.000 times. All numbers were pre-allocated in arrays, so calling custom code in constructors of decimal
and Money
should not affect the results.
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
Conclusions
decimal
are ~15 times slower than operations on long
or double
; division is ~30 times slower.Decimal
-like wrapper is better than performance of Decimal
but still significantly worse than performance of double
and long
due to lack of support from CLR.Decimal
in absolute numbers is quite fast: 40.000.000 operations per second.Advice
Decimal
with your own structure due to abcense of support from CLR. You might make it faster than Decimal
but it will never be as fast as double
.Decimal
is not enough for your application, than you might want consider switching your calculations to long
with fixed precision. Before returning the result to the client it should be converted to Decimal
.Upvotes: 17
Reputation: 3470
store "pennies" using double. apart from parsing input and printing outputs, you have the same speed you measured. you overcome the limit of 64 bit integer. you have a division not truncating. note : is up to you how to use the double result after divisions. this seems to me the simplest approach to your requirements.
Upvotes: 0
Reputation: 6368
Old question, still very valid though.
Here are some numbers to support the idea of using Long.
Time taken to perform 100'000'000 additions
Long 231 mS
Double 286 mS
Decimal 2010 mS
in a nutshell, decimal is ~10 times slower that Long or Double.
Code:
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
Upvotes: 2
Reputation: 2387
I don't think that SSE2 instructions could easy work with .NET Decimal values. .NET Decimal data type is 128bit decimal floating point type http://en.wikipedia.org/wiki/Decimal128_floating-point_format, SSE2 instructions work with 128bit integer types.
Upvotes: 3
Reputation:
What about MMX/SSE/SSE2?
i think it will help... so... decimal is 128bit datatype and SSE2 is 128bit too... and it can add, sub, div, mul decimal in 1 CPU tick...
you can write DLL for SSE2 using VC++ and then use that DLL in your application
e.g //you can do something like this
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);
}
Upvotes: 0
Reputation: 3509
I cannot give a comment or vote down yet since I just started on stack overflow. My comment on alexsmart (posted 23 Dec 2008 12:31) is that the expression Round(n/precision, precision), where n is int and precisions is long will not do what he thinks:
1) n/precision will return an integer-division, i.e. it will already be rounded but you won't be able to use any decimals. The rounding behavior is also different from Math.Round(...).
2) The code "return Math.Round(n/precision, precision).ToString()" does not compile due to an ambiguity between Math.Round(double, int) and Math.Round(decimal, int). You will have to cast to decimal (not double since it is a financial app) and therefore can as well go with decimal in the first place.
3) n/precision, where precision is 4 will not truncate to four decimals but divide by 4. E.g., Math.Round( (decimal) (1234567/4), 4) returns 308641. (1234567/4 = 308641.75), while what you probably wanted to to is get 1235000 (rounded to a precision of 4 digits up from the trailing 567). Note that Math.Round allows to round to a fixed point, not a fixed precision.
Update: I can add comments now but there is not enough space to put this one into the comment area.
Upvotes: 1
Reputation: 52679
you can use the long datatype. Sure, you won't be able to store fractions in there, but if you code your app to store pennies instead of pounds, you'll be ok. Accuracy is 100% for long datatypes, and unless you're working with vast numbers (use a 64-bit long type) you'll be ok.
If you can't mandate storing pennies, then wrap an integer in a class and use that.
Upvotes: 45
Reputation: 1502066
You say it needs to be fast, but do you have concrete speed requirements? If not, you may well optimise past the point of sanity :)
As a friend sitting next to me has just suggested, can you upgrade your hardware instead? That's likely to be cheaper than rewriting code.
The most obvious option is to use integers instead of decimals - where one "unit" is something like "a thousandth of a cent" (or whatever you want - you get the idea). Whether that's feasible or not will depend on the operations you're performing on the decimal values to start with. You'll need to be very careful when handling this - it's easy to make mistakes (at least if you're like me).
Did the profiler show particular hotspots in your application that you could optimise individually? For instance, if you need to do a lot of calculations in one small area of code, you could convert from decimal to an integer format, do the calculations and then convert back. That could keep the API in terms of decimals for the bulk of the code, which may well make it easier to maintain. However, if you don't have pronounced hotspots, that may not be feasible.
+1 for profiling and telling us that speed is a definite requirement, btw :)
Upvotes: 26
Reputation: 116421
The problem is basically that double/float are supported in hardware, while Decimal and the like are not. I.e. you have to choose between speed + limited precision and greater precision + poorer performance.
Upvotes: 8