round decimals c# performance decimal

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



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

  1. Las operaciones de suma, resta, multiplicación y comparación en decimal son ~ 15 veces más lentas que las operaciones en long o double ; la división es ~ 30 veces más lenta.
  2. El rendimiento de la envoltura tipo Decimal es mejor que el rendimiento de Decimal pero aún es significativamente peor que el rendimiento de double y long debido a la falta de soporte de CLR.
  3. Realizar cálculos en Decimal en números absolutos es bastante rápido: 40.000.000 de operaciones por segundo.

Consejo

  1. 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.
  2. 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 que Decimal pero nunca será tan rápido como el double .
  3. Si el rendimiento de Decimal no es suficiente para su aplicación, entonces puede considerar cambiar sus cálculos a long con una precisión fija. Antes de devolver el resultado al cliente, debe convertirse a Decimal .