Ich schreibe eine Finanzanwendung in C #, bei der die Leistung (d. H. Geschwindigkeit) entscheidend ist. Da es sich um eine Finanz-App handelt, muss ich den Dezimal-Datentyp intensiv verwenden.
Ich habe den Code so weit wie möglich mit Hilfe eines Profilers optimiert. Vor der Verwendung von Decimal wurde alles mit dem Double-Datentyp erledigt und die Geschwindigkeit war um ein Vielfaches höher. Double ist jedoch aufgrund seiner binären Natur keine Option, da es im Verlauf mehrerer Operationen zu vielen Präzisionsfehlern kommt.
Gibt es eine Dezimalbibliothek, die ich mit C # verbinden kann, was zu einer Leistungsverbesserung gegenüber dem nativen Decimal-Datentyp in .NET führen könnte?
Aufgrund der Antworten, die ich bereits erhalten habe, habe ich festgestellt, dass ich nicht klar genug war. Daher sind hier einige weitere Details aufgeführt:
Vielen Dank!
sie können den langen Datentyp verwenden. Sicher, Sie können dort keine Brüche speichern, aber wenn Sie Ihre App so programmieren, dass sie Pfennige statt Pfund speichert, sind Sie in Ordnung. Die Genauigkeit ist für lange Datentypen 100%. Wenn Sie nicht mit großen Zahlen arbeiten (verwenden Sie einen 64-Bit-langen Typ), sind Sie in Ordnung.
Wenn Sie nicht zwingen können, Pennies zu speichern, wickeln Sie eine Ganzzahl in eine Klasse ein und verwenden Sie diese.
Sie sagen, es muss schnell sein, aber haben Sie konkrete Geschwindigkeitsanforderungen? Wenn nicht, können Sie möglicherweise über den Punkt der Vernunft hinaus optimieren :)
Da ein Freund, der neben mir sitzt, gerade vorgeschlagen hat, können Sie stattdessen Ihre Hardware aufrüsten? Das ist wahrscheinlich billiger als das Umschreiben von Code.
Die naheliegendste Option ist die Verwendung von Ganzzahlen anstelle von Dezimalzahlen - wobei eine "Einheit" etwa "Tausendstel Cent" ist (oder was immer Sie wollen - Sie bekommen die Idee). Ob dies machbar ist oder nicht, hängt von den Vorgängen ab, mit denen Sie die Dezimalwerte beginnen. Sie müssen sehr vorsichtig sein, wenn Sie damit umgehen - es ist leicht, Fehler zu machen (zumindest wenn Sie wie ich sind).
Hat der Profiler in Ihrer Anwendung bestimmte Hotspots angezeigt, die Sie individuell optimieren konnten? Wenn Sie zum Beispiel viele Berechnungen in einem kleinen Codebereich ausführen müssen, können Sie das Dezimalformat in ein Ganzzahlformat konvertieren, die Berechnungen ausführen und dann zurückkonvertieren. Dies könnte dieAPIin Dezimalzahlen für den Großteil des Codes beibehalten, was die Wartung möglicherweise erleichtert. Wenn Sie jedoch keine Hotspots ausgesprochen haben, ist dies möglicherweise nicht machbar.
+1 für das Profiling und die Angabe, dass Geschwindigkeit eine eindeutige Anforderung ist, übrigens :)
Das Problem ist grundsätzlich, dass double/float in der Hardware unterstützt werden, Decimal und dergleichen jedoch nicht. Das heißt Sie müssen zwischen Geschwindigkeit + begrenzter Präzision und höherer Präzision + schlechterer Leistung wählen.
Die Frage ist gut diskutiert, aber da ich dieses Problem eine Weile durchgearbeitet habe, möchte ich einige meiner Ergebnisse mitteilen.
Problemdefinition: Es ist bekannt, dass Dezimalzahlen viel langsamer sind als Verdopplungen, aber Finanzanwendungen tolerieren keine Artefakte, die bei der Berechnung von Verdoppelungen auftreten.
Forschung
Mein Ziel war es, verschiedene Ansätze zum Speichern von Float-Pointing-Nummern zu messen und eine Schlussfolgerung zu ziehen, welche für unsere Anwendung verwendet werden sollte.
Wenn es für uns akzeptabel war, Int64
zu verwenden, um Fließkommazahlen mit fester Genauigkeit zu speichern. Der Multiplikator von 10 ^ 6 gab uns beide: genug Ziffern, um Brüche und einen großen Bereich für große Mengen zu speichern. Natürlich müssen Sie bei diesem Ansatz vorsichtig sein (Multiplikations- und Divisionsoperationen werden möglicherweise kompliziert), aber wir waren bereit und wollten diesen Ansatz auch messen. Abgesehen von möglichen Berechnungsfehlern und Überläufen müssen Sie beachten, dass Sie diese langen Zahlen normalerweise nicht der öffentlichen API aussetzen können. So können alle internen Berechnungen mit Longs durchgeführt werden, aber bevor die Nummern an den Benutzer gesendet werden, sollten sie in etwas freundlicheres umgewandelt werden.
Ich habe eine einfache Prototypklasse implementiert, die einen langen Wert in eine dezimalähnliche Struktur (genannt Money
) umschließt und den Messungen hinzufügt.
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
Ich habe folgende Operationen gemessen: Addition, Subtraktion, Multiplikation, Division, Gleichheitsvergleich und relativer (größerer/kleinerer) Vergleich. Ich habe Operationen mit folgenden Typen gemessen: double
, long
, decimal
und Money
. Jede Operation wurde 1.000.000 Mal durchgeführt. Alle Zahlen wurden in Arrays vorbelegt, sodass das Aufrufen von benutzerdefiniertem Code in den Konstruktoren von decimal
und Money
die Ergebnisse nicht beeinflussen sollte.
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
Schlussfolgerungen
decimal
sind ~ 15-mal langsamer als Operationen für long
oder double
; Division ist ~ 30 mal langsamer.Decimal
-like-Wrapper ist besser als die Leistung von Decimal
, aber immer noch erheblich schlechter als die Leistung von double
und long
, da die CLR keine Unterstützung bietet.Decimal
in absoluten Zahlen ist recht schnell: 40.000.000 Operationen pro Sekunde.Rat
Decimal
mit Ihrer eigenen Struktur neu zu implementieren, da CLR keine Unterstützung bietet. Sie machen es vielleicht schneller als Decimal
, aber es wird niemals so schnell sein wie double
.Decimal
für Ihre Anwendung nicht ausreicht, sollten Sie in Betracht ziehen, Ihre Berechnungen mit fester Genauigkeit auf long
umzustellen. Bevor das Ergebnis an den Client zurückgegeben wird, muss es in Decimal
konvertiert werden.Ich glaube nicht, dass SSE2-Anweisungen die Arbeit mit .NET Decimal-Werten erleichtern könnten. .NET Dezimal-Datentyp ist 128-Bit-Dezimal-Fließkomma type http://en.wikipedia.org/wiki/Decimal128_floating-point_format , SSE2-Anweisungen funktionieren mit 128-Bit-Integer-Typen .
Was ist mit MMX/SSE/SSE2?
ich denke, es wird helfen ... also ... dezimal ist 128-Bit-Datentyp und SSE2 ist auch 128-Bit ... und es kann in einem CPU-Tick Sub, Div, Mul Dezimal hinzufügen. ..
sie können DLL für SSE2 mit VC++ schreiben und dann dieses DLL in Ihrer Anwendung verwenden
z.B. // Sie können so etwas tun
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);
}
Alte Frage, immer noch sehr gültig.
Hier sind einige Zahlen, die die Idee der Verwendung von Long unterstützen.
Zeit, um 100'000'000 Ergänzungen durchzuführen
Long 231 mS
Double 286 mS
Decimal 2010 mS
kurz gesagt, die Dezimalzahl ist ~ 10-mal langsamer als Long oder 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
Ich kann noch keinen Kommentar abgeben oder abstimmen, da ich gerade mit dem Stapelüberlauf begonnen habe. Mein Kommentar zu Alexsmart (am 23. Dezember 2008 um 12:31 Uhr) besagt, dass der Ausdruck Round (n/precision, precision), wobei n int ist und die Genauigkeit lang ist, nicht das tut, was er denkt:
1) n/precision liefert eine Ganzzahl-Division, d. H. Sie wird bereits gerundet, es können jedoch keine Dezimalzahlen verwendet werden. Das Rundungsverhalten unterscheidet sich auch von Math.Round (...).
2) Der Code " return Math.Round (n/Genauigkeit, Genauigkeit) .ToString () " wird aufgrund einer Mehrdeutigkeit zwischen Math.Round (double, int) und Math.Round (decimal, int) nicht kompiliert. . Sie müssen in Dezimalzahlen umwandeln (nicht doppelt, da es sich um eine Finanz-App handelt) und kann daher auch in erster Linie mit Dezimalzahlen arbeiten.
3) n/Genauigkeit, wobei die Genauigkeit 4 ist, wird nicht auf vier Dezimalstellen verkürzt, sondern durch 4 dividiert. ZB Math.Round ((Dezimalzahl) (1234567/4), 4) gibt 308641 zurück (1234567/4 = 308641.75), während Sie wahrscheinlich 1235000 erhalten möchten (auf 4 Stellen genau gerundet als die nachstehenden 567). Beachten Sie, dass mit Math.Round ein fester Punkt gerundet werden kann und keine feste Genauigkeit.
Update: Ich kann jetzt Kommentare hinzufügen, aber es ist nicht genügend Platz vorhanden, um diesen Kommentar in den Kommentarbereich zu schreiben.
4 Jahre nach meiner vorherige Antwort Ich möchte eine weitere hinzufügen, die auf den Erfahrungen basiert, die wir im Laufe der Jahre mit Hochleistungsberechnungen mit Gleitkommazahlen gesammelt haben.
Es gibt zwei Hauptprobleme mit dem Datentyp Decimal
bei Hochleistungsberechnungen:
Während Sie in der ersten Ausgabe nicht viel tun können, ist die zweite Ausgabe noch wichtiger. Speicheroperationen und Prozessoren sind beim Betrieb mit 64-Bit-Zahlen äußerst effizient. 128-Bit-Operationen sind viel schwerer. Daher ist die .NET-Implementierung von Decimal
von Natur aus erheblich langsamer als die Operation von Double
, selbst für Lese-/Schreiboperationen.
Wenn Ihre Anwendung sowohl die Genauigkeit von Gleitkommaberechnungen als auch die Leistung solcher Operationen benötigt, sind weder Double
noch Decimal
für die Aufgabe geeignet. Die Lösung, die wir in meinem Unternehmen (Fintech-Domäne) übernommen haben, ist die Verwendung eines Wrappers über Intel® Decimal Floating-Point Math Library . Es implementiert die IEEE 754-2008 Decimal Floating-Point Arithmetic specification
Bereitstellen von 64-Bit-Gleitkommadezimalzahlen.
Bemerkungen. Decimals
sollte nur zum Speichern von Gleitkommazahlen und einfachen Rechenoperationen verwendet werden. Alle umfangreichen mathematischen Berechnungen wie die Berechnung von Indikatoren für die technische Analyse sollten mit Double
Werten durchgeführt werden.
speichern Sie "Pennys" mit double. Abgesehen von der Analyse von Eingabe- und Druckausgaben haben Sie dieselbe Geschwindigkeit, die Sie gemessen haben. Sie überwinden die Grenze von 64-Bit-Ganzzahl. Sie haben eine Abteilung, die nicht abschneidet. Hinweis: Es liegt an Ihnen, wie Sie das Doppelergebnis nach Abteilungen verwenden. Dies scheint mir die einfachste Herangehensweise an Ihre Anforderungen.