it-swarm.com.de

C # Dezimaler Datentyp

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:

  • Die App muss so schnell wie möglich sein (d. H. So schnell wie bei der Verwendung von Double anstelle von Decimal wäre ein Traum). Double war etwa 15x schneller als Decimal, da die Operationen hardwarebasiert sind.
  • Die Hardware ist bereits erstklassig (ich arbeite auf einem Dual Xenon Quad-Core) und die Anwendung verwendet Threads, sodass die CPU-Auslastung auf dem Computer immer 100% beträgt. Darüber hinaus läuft die App im 64-Bit-Modus, was einen messbaren Leistungsvorteil gegenüber 32-Bit bietet.
  • Ich habe über den Punkt der Vernunft hinaus optimiert (mehr als eineinhalb Monate Optimierung; ob Sie es glauben oder nicht, es dauert jetzt etwa 1/5000 von dem, was nötig war, um die gleichen Berechnungen durchzuführen, die ich anfangs als Referenz verwendete). Diese Optimierung umfasste alles: Zeichenfolgenverarbeitung, E/A, Datenbankzugriff und -indizes, Speicher, Schleifen, Änderung der Art und Weise, wie einige Dinge gemacht wurden, und sogar "switch" over "if" überall, wo es einen Unterschied machte. Der Profiler zeigt jetzt deutlich, dass der verbleibende Leistungsverursacher die Dezimal-Datentypoperatoren ist. Nichts anderes kostet eine beträchtliche Zeit.
  • Sie müssen mir hier glauben: Ich bin so weit gegangen, wie ich es im Bereich von C # .NET tun konnte, um die Anwendung zu optimieren, und ich bin wirklich erstaunt über die aktuelle Leistung. Ich bin jetzt auf der Suche nach einer guten Idee, um die Decimal-Leistung so zu verbessern, dass sie nahe an Double liegt. Ich weiß, es ist nur ein Traum, aber ich wollte nur überprüfen, dass ich an alles Mögliche dachte. :)

Vielen Dank!

53
tempw

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.

40
gbjbaanb

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 :)

22
Jon Skeet

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. 

8
Brian Rasmussen

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

  1. Additions-, Subtraktions-, Multiplikations- und Vergleichsoperationen für decimal sind ~ 15-mal langsamer als Operationen für long oder double; Division ist ~ 30 mal langsamer.
  2. Die Leistung von 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.
  3. Die Berechnung von Decimal in absoluten Zahlen ist recht schnell: 40.000.000 Operationen pro Sekunde.

Rat

  1. Verwenden Sie Dezimalzahlen, es sei denn, Sie haben einen sehr schweren Berechnungsfall. In relativen Zahlen sind sie langsamer als long und double, aber absolute Zahlen sehen gut aus.
  2. Es hat nicht viel Sinn, 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.
  3. Wenn die Leistung von 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.
5
user1921819

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 .

3
Sergey Shandar

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

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
2
smirkingman

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.

1
ILoveFortran

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:

  1. CLR behandelt diesen Typ als reguläre Struktur (keine spezielle Unterstützung wie bei anderen integrierten Typen).
  2. In ist 128 Bit

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.

0
user1921819

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.

0
Massimo