私はC#で金融アプリケーションを作成していますが、パフォーマンス(速度)が重要です。これは金融アプリなので、Decimalデータ型を集中的に使用する必要があります。
プロファイラーの助けを借りて、できる限りコードを最適化しました。 Decimalを使用する前は、すべてがDoubleデータ型で処理され、速度は数倍高速でした。ただし、Doubleはバイナリの性質を備えているため、オプションではありません。複数の操作を実行すると、多くの精度エラーが発生します。
.NETのネイティブのDecimalデータ型よりもパフォーマンスを向上させることができる、C#とインターフェイスできる10進数ライブラリはありますか?
私がすでに得た答えに基づいて、私は十分に明確ではないことに気づいたので、ここにいくつかの追加の詳細があります:
ありがとう!
longデータ型を使用できます。もちろん、端数をそこに格納することはできませんが、ポンドではなくペニーを格納するようにアプリをコーディングすれば、大丈夫です。 longデータ型の精度は100%であり、膨大な数を扱う場合(64ビットのlong型を使用する場合)は問題ありません。
ペニーの保存を義務付けることができない場合は、クラスに整数をラップして使用します。
高速にする必要があるとおっしゃっていますが、具体的な速度要件はありますか?そうでない場合は、健全性のポイントを超えて最適化することもできます。
私の隣に座っている友人が提案したように、代わりにハードウェアをアップグレードできますか?それはコードを書き換えるよりも安くなるでしょう。
最も明白なオプションは、小数ではなく整数を使用することです。1つの「単位」は、「1000分の1セント」のようなものです(または、あなたが望むものは何でも-アイデアがわかります)。それが可能かどうかは、最初に10進数値に対して実行している演算によって異なります。これを扱うときはvery注意する必要があります-間違いを犯しやすいです(少なくとも、私のような場合)。
プロファイラーは、個別に最適化できる特定のホットスポットをアプリケーションに表示しましたか?たとえば、コードの1つの小さな領域で多くの計算を行う必要がある場合、10進数から整数形式に変換し、計算を行ってから元に戻すことができます。 [〜#〜] api [〜#〜]をコードの大部分の小数で保持できるため、保守が容易になる可能性があります。ただし、ホットスポットが目立たない場合は、実行できない可能性があります。
プロファイリングおよび速度が明確な要件であることを私たちに伝えるための+ 1、btw :)
問題は基本的にdouble/floatがハードウェアでサポートされているのに対し、Decimalなどはサポートされていないことです。つまり速度+精度の制限と精度の向上+パフォーマンスの低下のどちらかを選択する必要があります。
質問はよく議論されていますが、しばらくの間この問題を掘り下げていたので、私の結果のいくつかを共有したいと思います。
問題の定義:小数は倍精度よりもはるかに遅いことが知られていますが、金融アプリケーションは、倍精度で計算を実行したときに発生するアーティファクトを許容できません。
研究
私の目的は、浮動小数点数を格納するさまざまな方法を測定し、アプリケーションにどの方法を使用すべきかを結論付けることでした。
Int64
は、固定精度で浮動小数点数を格納します。 10 ^ 6の乗数は、小数を格納するのに十分な桁数と、大量を格納するのに大きな範囲をスティルするという両方を与えてくれました。もちろん、このアプローチには注意が必要です(乗算および除算の操作はトリッキーになる可能性があります)が、私たちは準備ができており、このアプローチも測定したいと考えていました。考えられる計算エラーとオーバーフローを除いて、覚えておかなければならないことの1つは、通常、これらの長い数値をパブリックAPIに公開できないことです。したがって、すべての内部計算はlongで実行できますが、数値をユーザーに送信する前に、よりわかりやすいものに変換する必要があります。
Long値を10進数のような構造(Money
と呼ばれます)にラップする単純なプロトタイプクラスを実装し、測定に追加しました。
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);
}
}
実験
私は次の操作を測定しました:加算、減算、乗算、除算、等値比較、および相対的(より大きい/少ない)比較。次のタイプの操作を測定していました:double
、long
、decimal
およびMoney
。各操作は1.000.000回実行されました。すべての数値は配列に事前に割り当てられているため、decimal
およびMoney
のコンストラクターでカスタムコードを呼び出しても結果には影響しません。
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
結論
decimal
での加算、減算、乗算、比較演算は、long
またはdouble
での演算よりも最大15倍遅くなります。除算は約30倍遅くなります。Decimal
のようなラッパーのパフォーマンスはDecimal
のパフォーマンスよりも優れていますが、CLRからのサポートが不足しているため、double
およびlong
のパフォーマンスよりも大幅に低下しています。Decimal
に対して絶対数で計算を実行すると、1秒あたり40.000.000オペレーションと非常に高速になります。アドバイス
Decimal
を再実装する意味はあまりありません。 Decimal
より高速にすることもできますが、double
ほど高速になることはありません。Decimal
のパフォーマンスがアプリケーションにとって十分でない場合は、固定精度で計算をlong
に切り替えることを検討してください。結果をクライアントに返す前に、Decimal
に変換する必要があります。SSE2命令が.NET Decimal値で簡単に機能することはないと思います。 .NET Decimalデータ型は128ビット10進浮動小数点型 http://en.wikipedia.org/wiki/Decimal128_floating-point_format =、SSE2命令は128ビット整数型で動作します。
MMX/SSE/SSE2はどうですか?
私はそれが役立つと思います...だから... 10進数は128ビットのデータ型であり、SSE2も128ビットです...そして、それは1 CPUティックで10進数を追加、サブ、分割、追加できます...
DLL SSE2の場合はVC++を使用して記述し、アプリケーションでそのDLLを使用して
例//次のようなことができます
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);
}
古い質問ですが、それでも非常に有効です。
Longを使用するという考えを裏付けるいくつかの数値を以下に示します。
100'000'000追加を実行するのにかかる時間
Long 231 mS
Double 286 mS
Decimal 2010 mS
簡単に言えば、10進数はLongまたはDoubleよりも10倍遅くなります。
コード:
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
スタックオーバーフローを開始したばかりなので、コメントや投票はまだできません。 alexsmart(2008年12月23日12:31投稿)に関する私のコメントは、nがintで精度が長い式Round(n/precision、precision)は、彼が思っていることを実行しないというものです。
1)n/precisionは整数の除算を返します。つまり、すでに丸められていますが、小数は使用できません。丸めの動作もMath.Round(...)とは異なります。
2)Math.Round(double、int)とMath.Round(decimal)のあいまいさのため、コード "return Math.Round(n/precision、precision).ToString()"はコンパイルされません。 、int)。 10進数にキャストする必要があります(金融アプリであるため、2倍ではありません)。したがって、最初に10進数を使用することもできます。
3)精度が4であるn /精度は、小数点以下4桁に切り捨てられませんが、4で除算されます。例:Math.Round((decimal)(1234567/4)、4)は308641を返します。(1234567/4 = 308641.75)、あなたがおそらく望んでいたのは1235000を取得することです(末尾の567から4桁の精度に丸められます)。 Math.Roundでは、固定精度ではなく、固定小数点に丸めることができることに注意してください。
更新:ここでコメントを追加できますが、コメント領域にこれを配置するための十分なスペースがありません。
私の4年後 前の回答 浮動小数点数を使用した高性能計算の操作に関する長年の経験に基づいて、もう1つ追加したいと思います。
高性能計算でのDecimal
データ型には2つの大きな問題があります。
最初の問題については多くのことを行うことはできませんが、2番目の問題はさらに重要に見えます。 64ビットの数値で操作する場合、メモリ操作とプロセッサは非常に効率的です。 128ビット操作ははるかに重いです。したがって、Decimal
の.NET実装は、読み取り/書き込み操作であっても、Double
での操作よりも設計上大幅に遅くなります。
アプリケーションで浮動小数点計算の精度とそのような演算のパフォーマンスの両方が必要な場合、Double
もDecimal
もタスクに適していません。私の会社(Fintechドメイン)で採用したソリューションは、 Intel®Decimal Floating-Point Math Library の上にラッパーを使用することです。 IEEE 754-2008 Decimal Floating-Point Arithmetic specification
64ビット浮動小数点10進数を提供します。
備考。 Decimals
は、浮動小数点数とそれらに対する単純な算術演算の格納にのみ使用してください。テクニカル分析のための指標の計算のようなすべての重い数学は、Double
値に対して実行する必要があります。
ダブルを使用して「ペニー」を格納します。入力の解析と出力の出力は別として、測定した速度と同じです。 64ビット整数の制限を克服します。あなたは切り捨てない部門を持っています注:除算後の二重結果の使用方法はあなた次第です。これはあなたの要件への最も簡単なアプローチのようです。