decimals - double vs decimal c#
C#Decimal datatype performance (9)
El problema es básicamente que double / float es compatible con hardware, mientras que Decimal y similares no lo son. Es decir, debe elegir entre velocidad + precisión limitada y mayor precisión + peor rendimiento.
Estoy escribiendo una aplicación financiera en C # donde el rendimiento (es decir, la velocidad) es crítico. Debido a que es una aplicación financiera, tengo que usar el tipo de datos Decimal intensivamente.
Optimicé el código tanto como pude con la ayuda de un generador de perfiles. Antes de usar Decimal, todo estaba hecho con el tipo de datos Double y la velocidad era varias veces más rápida. Sin embargo, Double no es una opción debido a su naturaleza binaria, lo que causa una gran cantidad de errores de precisión en el transcurso de múltiples operaciones.
¿Hay alguna biblioteca de decimales con la que pueda interactuar con C # que pueda darme una mejora en el rendimiento sobre el tipo de datos Decimal nativo en .NET?
Basado en las respuestas que ya obtuve, noté que no era lo suficientemente claro, así que aquí hay algunos detalles adicionales:
- La aplicación debe ser lo más rápida posible (es decir, tan rápido como lo era cuando se usaba Double en lugar de Decimal como un sueño). El doble fue aproximadamente 15 veces más rápido que el decimal, ya que las operaciones están basadas en hardware.
- El hardware ya es de primera categoría (estoy ejecutando en un Dual-Xenon Quad-Core) y la aplicación utiliza subprocesos, por lo que la utilización de la CPU siempre es del 100% en la máquina. Además, la aplicación se ejecuta en modo de 64 bits, lo que le da una ventaja de rendimiento medible de más de 32 bits.
- He optimizado más allá del punto de cordura (más de un mes y medio optimizando; créanlo o no, ahora se necesitan aproximadamente 1/5000 de lo que se necesitó para hacer los mismos cálculos que usé inicialmente como referencia); Esta optimización involucraba todo: procesamiento de cadenas, E / S, acceso e índices a la base de datos, memoria, bucles, cambiando la forma en que se hacían algunas cosas, e incluso utilizando "cambiar" por "si" marcó una diferencia. El generador de perfiles ahora muestra claramente que el culpable de rendimiento restante está en los operadores de tipo de datos Decimal. Nada más está sumando una cantidad considerable de tiempo.
- Tienes que creerme aquí: he ido tan lejos como pude en el ámbito de C # .NET para optimizar la aplicación, y estoy realmente sorprendido por su rendimiento actual. Ahora estoy buscando una buena idea para mejorar el rendimiento del decimal a algo cercano al doble. Sé que es solo un sueño, pero solo quería comprobar que pensé en todo lo posible. :)
¡Gracias!
Usted dice que debe ser rápido, pero ¿tiene requisitos concretos de velocidad? Si no, puedes optimizar más allá del punto de cordura :)
Como un amigo sentado a mi lado acaba de sugerir, ¿puedes actualizar tu hardware? Es probable que sea más barato que volver a escribir el código.
La opción más obvia es usar números enteros en lugar de decimales, donde una "unidad" es algo así como "una milésima de un centavo" (o lo que quieras, entiendes la idea). Si eso es factible o no dependerá de las operaciones que está realizando en los valores decimales para empezar. Tendrás que tener mucho cuidado al manejar esto; es fácil cometer errores (al menos si eres como yo).
¿El perfilador muestra puntos de acceso específicos en su aplicación que puede optimizar de forma individual? Por ejemplo, si necesita hacer muchos cálculos en un área pequeña de código, puede convertir de formato decimal a entero, hacer los cálculos y luego convertir de nuevo. Eso podría mantener la API en términos de decimales para la mayor parte del código, lo que bien puede hacer que sea más fácil de mantener. Sin embargo, si no tiene hotspots pronunciados, eso puede no ser factible.
+1 para perfilar y decirnos que la velocidad es un requisito definitivo, por cierto :)
puedes usar el tipo de datos largo. Claro, no podrás almacenar fracciones allí, pero si codificas tu aplicación para almacenar centavos en lugar de libras, estarás bien. La precisión es del 100% para los tipos de datos largos, y a menos que trabaje con un gran número (use un tipo de 64 bits de longitud), estará bien.
Si no puede obligar a almacenar centavos, envuelva un número entero en una clase y utilícelo.
No puedo dar un comentario o votar todavía porque recién comencé a desbordar la pila. Mi comentario sobre alexsmart (publicado el 23 de diciembre de 2008 12:31) es que la expresión Round (n / precisión, precisión), donde n es int y precisiones largas, no hará lo que él piensa:
1) n / precision devolverá una división entera, es decir, ya estará redondeada pero no podrá usar decimales. El comportamiento de redondeo también es diferente de Math.Round (...).
2) El código " return Math.Round (n / precision, precision) .ToString () " no se compila debido a una ambigüedad entre Math.Round (double, int) y Math.Round (decimal, int). Tendrá que convertir a decimal (no doble ya que es una aplicación financiera) y, por lo tanto, también puede ir con decimal en primer lugar.
3) n / precision, donde la precisión es 4 no truncará a cuatro decimales, sino que se dividirá por 4. Por ejemplo, Math.Round ((decimal) (1234567/4), 4) devuelve 308641. (1234567/4 = 308641.75), mientras lo que probablemente desee es obtener 1235000 (redondeado a una precisión de 4 dígitos desde el final de 567). Tenga en cuenta que Math.Round permite redondear a un punto fijo, no a una precisión fija.
Actualización: ahora puedo agregar comentarios pero no hay suficiente espacio para poner este en el área de comentarios.
¿Qué hay de MMX / SSE / SSE2?
creo que ayudará ... así que ... decimal es el tipo de datos de 128 bits y SSE2 también es de 128 bits ... y puede agregar, sub, div, mul decimal en 1 tilde de la CPU ...
puedes escribir DLL para SSE2 usando VC ++ y luego usar esa DLL en tu aplicación
ej. // puedes hacer algo como esto
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;
}
DO#
[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);
}
No creo que las instrucciones SSE2 puedan trabajar fácilmente con los valores .NET Decimal. El tipo de datos decimales .NET es un tipo de coma flotante decimal de 128 bits http://en.wikipedia.org/wiki/Decimal128_floating-point_format , las instrucciones SSE2 funcionan con tipos enteros de 128 bits .
Una vieja pregunta, aún muy válida.
Aquí hay algunos números para apoyar la idea de usar Long.
Tiempo necesario para realizar 100''000''000 adiciones
Long 231 mS
Double 286 mS
Decimal 2010 mS
en pocas palabras, el decimal es ~ 10 veces más lento que el largo o el doble.
Código:
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
almacenar "centavos" con doble. aparte de analizar las salidas de entrada e impresión, tiene la misma velocidad que midió. superas el límite del entero de 64 bits. tienes una división que no trunca. nota: depende de usted cómo usar el doble resultado después de las divisiones. esto me parece el enfoque más simple para sus requisitos.
La pregunta está bien discutida, pero dado que estuve cavando este problema por un tiempo, me gustaría compartir algunos de mis resultados.
Definición del problema: Se sabe que los decimales son mucho más lentos que los dobles, pero las aplicaciones financieras no pueden tolerar ningún artefacto que surja cuando los cálculos se realizan en dobles.
Investigación
Mi objetivo era medir diferentes enfoques de almacenamiento de los números que apuntan al flotador y hacer una conclusión sobre cuál debería usarse para nuestra aplicación.
Si fue aceptable para nosotros, usamos Int64
para almacenar números de coma flotante con precisión fija. El multiplicador de 10 ^ 6 nos daba los dos: suficientes dígitos para almacenar fracciones y un gran rango para almacenar grandes cantidades. Por supuesto, hay que tener cuidado con este enfoque (las operaciones de multiplicación y división pueden ser complicadas), pero estábamos listos y queríamos medir este enfoque también. Una cosa que debes tener en cuenta, salvo los posibles errores de cálculo y desbordamientos, es que generalmente no puedes exponer esos números largos a la API pública. Por lo tanto, todos los cálculos internos podrían realizarse con largos, pero antes de enviar los números al usuario, deberían convertirse en algo más amigable.
Implementé una clase prototipo simple que envuelve un valor largo a una estructura decimal (lo llamó Money
) y lo agregué a las medidas.
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);
}
}
Experimentar
Medí las siguientes operaciones: suma, resta, multiplicación, división, comparación de igualdad y comparación relativa (mayor / menor). Estaba midiendo operaciones en los siguientes tipos: double
, long
, decimal
y Money
. Cada operación se realizó 1.000.000 de veces. Todos los números se preasignan en matrices, por lo que llamar al código personalizado en los constructores de decimal
y Money
no debería afectar los resultados.
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
Conclusiones
- Las operaciones de suma, resta, multiplicación y comparación en
decimal
son ~ 15 veces más lentas que las operaciones enlong
odouble
; la división es ~ 30 veces más lenta. - El rendimiento de la envoltura tipo
Decimal
es mejor que el rendimiento deDecimal
pero aún es significativamente peor que el rendimiento dedouble
ylong
debido a la falta de soporte de CLR. - Realizar cálculos en
Decimal
en números absolutos es bastante rápido: 40.000.000 de operaciones por segundo.
Consejo
- A menos que tenga un caso de cálculo muy pesado, use decimales. En números relativos, son más lentos que los largos y los dobles, pero los números absolutos se ven bien.
- No tiene mucho sentido volver a implementar
Decimal
con su propia estructura debido a la falta de soporte de CLR. Puede hacer que sea más rápido queDecimal
pero nunca será tan rápido como eldouble
. - Si el rendimiento de
Decimal
no es suficiente para su aplicación, entonces puede considerar cambiar sus cálculos along
con una precisión fija. Antes de devolver el resultado al cliente, debe convertirse aDecimal
.