La pregunta es bien discutida, pero desde 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 fue medir diferentes enfoques de almacenamiento de números flotantes-señalar y hacer una conclusión a la que uno debe usarse para nuestra aplicación.
Si aceptamos usar Int64
para almacenar números de punto 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 de prototipo simple que ajusta un valor largo a una estructura decimal (lo llamó Money
) y lo agregué a las mediciones.
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);
}
}
Experimento
Medí operaciones siguientes: suma, resta, multiplicación, división, comparación de igualdad y (mayor/menor) comparación relativa. 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 asignaron previamente 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
- suma, resta, multiplicación, operaciones de comparación en
decimal
son ~ 15 veces más lento que las operaciones en long
o double
; la división es ~ 30 veces más lenta.
- El rendimiento de
Decimal
-como el envoltorio 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.
- La realización de cálculos en
Decimal
en números absolutos es bastante rápida: 40.000.000 de operaciones por segundo.
consejos
- A menos que tenga un caso de cálculo muy pesado, usar decimales. En números relativos, son más lentos que los largos y 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 que Decimal
, pero nunca será tan rápido como double
.
- Si el rendimiento de
Decimal
no es suficiente para su aplicación, entonces puede considerar cambiar sus cálculos a long
con precisión fija. Antes de devolver el resultado al cliente, debe convertirse a Decimal
.
Usted está a punto de realizar una optimización prematura, simplemente codifique la aplicación correctamente, luego modifíquela después del hecho – TravisO
Ha realizado todas las optimizaciones de bajo nivel de C#. Puede haber algunas mejoras algorítmicas restantes (por ejemplo, hacer menos operaciones en los decimales). – Brian
Las operaciones de la base de datos en una aplicación financiera sensible al tiempo suena mal ... –