J'écris une application financière en C # où la performance (c'est-à-dire la vitesse) est essentielle. Parce que c'est une application financière, je dois utiliser le type de données Decimal de manière intensive.
J'ai optimisé le code autant que possible avec l'aide d'un profileur. Avant d'utiliser Decimal, tout était fait avec le type de données Double et la vitesse était plusieurs fois plus rapide. Toutefois, Double n’est pas une option en raison de sa nature binaire, ce qui entraîne de nombreuses erreurs de précision au cours de plusieurs opérations.
Existe-t-il une bibliothèque décimale que je peux interfacer avec C # qui pourrait améliorer considérablement les performances par rapport au type de données Decimal natif dans .NET?
Sur la base des réponses que j'ai déjà obtenues, j'ai remarqué que je n'étais pas assez clair. Voici donc quelques détails supplémentaires:
Merci!
vous pouvez utiliser le type de données long. Bien sûr, vous ne pourrez pas y stocker de fractions, mais si vous codez votre application pour qu'elle stocke des sous au lieu de deux kilos, tout ira bien. La précision est de 100% pour les types de données longs et, à moins que vous ne travailliez avec de grands nombres (utilisez un type long de 64 bits), tout ira bien.
Si vous ne pouvez pas imposer de stocker des sous, insérez un entier dans une classe et utilisez-le.
Vous dites que cela doit être rapide, mais avez-vous des exigences concrètes en matière de vitesse? Sinon, vous pouvez optimiser le passé.
Comme un ami assis à côté de moi vient de le suggérer, pouvez-vous mettre à niveau votre matériel? C'est probablement moins cher que de réécrire du code.
L'option la plus évidente consiste à utiliser des nombres entiers au lieu de décimales - où une "unité" est quelque chose comme "un millième de cent" (ou ce que vous voulez - vous voyez l'idée). Que cela soit réalisable ou non dépendra des opérations que vous effectuez sur les valeurs décimales pour commencer. Vous devrez être très prudent lorsque vous vous en occuperez - il est facile de faire des erreurs (du moins si vous êtes comme moi).
Le profileur a-t-il indiqué dans votre application des zones sensibles que vous pouviez optimiser individuellement? Par exemple, si vous devez effectuer de nombreux calculs dans une petite zone de code, vous pouvez convertir du format décimal au format entier, effectuer les calculs puis reconvertir. Cela pourrait garder leAPIen termes de nombres décimaux pour l’essentiel du code, ce qui pourrait en faciliter la maintenance. Cependant, si vous n'avez pas de points chauds prononcés, cela risque de ne pas être réalisable.
+1 pour le profilage et nous dire que la vitesse est une exigence définie, au fait :)
Le problème est fondamentalement que les doubles/float sont supportés dans le matériel, alors que Decimal et autres ne le sont pas. C'est à dire. vous devez choisir entre vitesse + précision limitée et plus grande précision + performances plus faibles.
La question est bien discutée, mais depuis que je creuse ce problème depuis un moment, je voudrais partager certains de mes résultats.
Définition du problème: Les décimales sont connues pour être beaucoup plus lentes que les doubles, mais les applications financières ne peuvent tolérer aucun artefact pouvant survenir lorsque les calculs sont effectués sur des doubles.
Recherche
Mon but était de mesurer différentes approches de stockage de nombres à virgule flottante et de tirer une conclusion à utiliser pour notre application.
Si nous pouvions utiliser Int64
pour stocker des nombres à virgule flottante avec une précision fixe. Un multiplicateur de 10 ^ 6 nous donnait les deux: assez de chiffres pour stocker des fractions et une large plage pour stocker de grandes quantités. Bien sûr, vous devez faire attention à cette approche (les opérations de multiplication et de division peuvent devenir délicates), mais nous étions prêts et voulions également mesurer cette approche. Une chose à ne pas oublier, à l'exception des erreurs de calcul et des débordements possibles, est que vous ne pouvez généralement pas exposer ces nombres longs à des API publiques. Tous les calculs internes peuvent donc être effectués avec des longs, mais avant d'envoyer les numéros à l'utilisateur, ils doivent être convertis en quelque chose de plus convivial.
J'ai implémenté une classe de prototype simple qui englobe une valeur longue dans une structure de type décimale (appelée Money
) et l'a ajoutée aux mesures.
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);
}
}
Expérience
J'ai mesuré les opérations suivantes: addition, soustraction, multiplication, division, comparaison d'égalité et comparaison relative (plus/moins). Je mesurais des opérations sur les types suivants: double
, long
, decimal
et Money
. Chaque opération a été effectuée 1 000 000 fois. Tous les numéros ont été pré-alloués dans des tableaux. Par conséquent, l'appel de code personnalisé dans les constructeurs de decimal
et Money
ne devrait pas affecter les résultats.
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
sont environ 15 fois plus lentes que les opérations sur long
ou double
; la division est environ 30 fois plus lente.Decimal
- like sont meilleures que celles de Decimal
, mais néanmoins nettement inférieures à celles de double
et long
en raison du manque de soutien de la part du CLR.Decimal
en nombres absolus est assez rapide: 40 000 000 opérations par seconde.Conseil
Decimal
avec votre propre structure en raison de l’absence de soutien de la part du CLR. Vous pourriez le rendre plus rapide que Decimal
mais ce ne sera jamais aussi rapide que double
.Decimal
ne suffisent pas pour votre application, vous pouvez envisager de passer vos calculs à long
avec une précision fixe. Avant de renvoyer le résultat au client, il doit être converti en Decimal
.Je ne pense pas que les instructions SSE2 pourraient facilement fonctionner avec les valeurs décimales .NET. Le type de données .NET décimal est virgule flottante décimale 128 bits type http://en.wikipedia.org/wiki/Decimal128_floating-point_format , les instructions SSE2 fonctionnent avec types d'entiers 128 bits .
Qu'en est-il de MMX/SSE/SSE2?
je pense que cela aidera ... donc ... decimal est un type de données de 128 bits et SSE2 est aussi de 128 bits ... et il peut ajouter, sous, div, mul décimal en 1 tick CPU. ..
vous pouvez écrire DLL pour SSE2 à l'aide de VC++, puis utiliser ce DLL dans votre application
par exemple // vous pouvez faire quelque chose comme ceci
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);
}
Vieille question, toujours très valable cependant.
Voici quelques chiffres à l’appui de l’idée d’utiliser Long.
Temps nécessaire pour effectuer 100'000'000 ajouts
Long 231 mS
Double 286 mS
Decimal 2010 mS
en un mot, le point décimal est environ 10 fois plus lent que le long ou le 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
Je ne peux pas encore commenter ni voter, car je viens juste de commencer avec un débordement de pile. Mon commentaire sur alexsmart (23 décembre 2008 12:31) est que l'expression Round (n/precision, precision), où n est int et precisions est longue ne fera pas ce qu'il pense:
1) n/precision retournera une division entière, c’est-à-dire qu’elle sera déjà arrondie, mais vous ne pourrez utiliser aucune décimale. Le comportement d'arrondi est également différent de Math.Round (...).
2) Le code " return Math.Round (n/precision, precision) .ToString () " ne compile pas en raison d'une ambiguïté entre Math.Round (double, int) et Math.Round (decimal, int) . Vous devrez choisir un nombre décimal (et non doubler puisqu'il s'agit d'une application financière). Vous pouvez donc également utiliser le format décimal en premier lieu.
3) n/precision, où la précision est 4 et ne sera pas tronqué à quatre décimales mais divisé par 4. Par exemple, Math.Round ((décimal) (1234567/4), 4) renvoie 308641. (1234567/4 = 308641.75), alors que vous souhaitiez probablement obtenir 1235000 (arrondi à une précision de 4 chiffres à partir du 567 suivant). Notez que Math.Round permet d’arrondir à un point fixe, pas à une précision fixe.
Mise à jour: Je peux ajouter des commentaires maintenant, mais il n'y a pas assez d'espace pour placer celui-ci dans la zone de commentaire.
magasin "pennies" en utilisant double. à part l'analyse des entrées et l'impression des sorties, vous avez la même vitesse que vous avez mesurée. vous surmontez la limite de 64 bits. vous avez une division ne tronquant pas. remarque: à vous de choisir comment utiliser le double résultat après les divisions. cela me semble l’approche la plus simple à vos besoins.