web-dev-qa-db-ja.com

C#の10進データ型のパフォーマンス

私はC#で金融アプリケーションを作成していますが、パフォーマンス(速度)が重要です。これは金融アプリなので、Decimalデータ型を集中的に使用する必要があります。

プロファイラーの助けを借りて、できる限りコードを最適化しました。 Decimalを使用する前は、すべてがDoubleデータ型で処理され、速度は数倍高速でした。ただし、Doubleはバイナリの性質を備えているため、オプションではありません。複数の操作を実行すると、多くの精度エラーが発生します。

.NETのネイティブのDecimalデータ型よりもパフォーマンスを向上させることができる、C#とインターフェイスできる10進数ライブラリはありますか?

私がすでに得た答えに基づいて、私は十分に明確ではないことに気づいたので、ここにいくつかの追加の詳細があります:

  • アプリは可能な限り高速でなければなりません(つまり、Decimalの代わりにDoubleを使用するのが夢だったときと同じくらい高速である必要があります)。演算はハードウェアベースであるため、DoubleはDecimalの約15倍高速でした。
  • ハードウェアはすでに一流(私はデュアルXenonクアッドコアで実行しています)であり、アプリケーションはスレッドを使用するため、マシンのCPU使用率は常に100%です。さらに、アプリは64ビットモードで実行されているため、32ビットよりもパフォーマンスが大幅に向上しています。
  • 私は正真正銘のポイントを過ぎて最適化しました(1か月半以上の最適化。信じられないかもしれませんが、最初に参照として使用したのと同じ計算を行うのに要した時間の約1/5000がかかります)。この最適化にはすべてが含まれます:文字列処理、I/O、データベースアクセスとインデックス、メモリ、ループ、何かが行われた方法の変更、そしてどこでも "switch"を "if"で使用することで違いが生じます。プロファイラーは、残りのパフォーマンスの原因がDecimalデータ型演算子にあることを明確に示しています。相当な時間を追加しているものは他にありません。
  • ここで私を信じてください。アプリケーションを最適化するためにC#.NETの領域に入る可能性があるところまで行ってきましたが、現在のパフォーマンスには本当に驚いています。 DecimalのパフォーマンスをDoubleに近い値に改善するための良いアイデアを探しています。私はそれがただの夢であることを知っていますが、私がチェックしたかったのは、可能な限りすべてを考えていたということです。 :)

ありがとう!

54
tempw

longデータ型を使用できます。もちろん、端数をそこに格納することはできませんが、ポンドではなくペニーを格納するようにアプリをコーディングすれば、大丈夫です。 longデータ型の精度は100%であり、膨大な数を扱う場合(64ビットのlong型を使用する場合)は問題ありません。

ペニーの保存を義務付けることができない場合は、クラスに整数をラップして使用します。

41
gbjbaanb

高速にする必要があるとおっしゃっていますが、具体的な速度要件はありますか?そうでない場合は、健全性のポイントを超えて最適化することもできます。

私の隣に座っている友人が提案したように、代わりにハードウェアをアップグレードできますか?それはコードを書き換えるよりも安くなるでしょう。

最も明白なオプションは、小数ではなく整数を使用することです。1つの「単位」は、「1000分の1セント」のようなものです(または、あなたが望むものは何でも-アイデアがわかります)。それが可能かどうかは、最初に10進数値に対して実行している演算によって異なります。これを扱うときはvery注意する必要があります-間違いを犯しやすいです(少なくとも、私のような場合)。

プロファイラーは、個別に最適化できる特定のホットスポットをアプリケーションに表示しましたか?たとえば、コードの1つの小さな領域で多くの計算を行う必要がある場合、10進数から整数形式に変換し、計算を行ってから元に戻すことができます。 [〜#〜] api [〜#〜]をコードの大部分の小数で保持できるため、保守が容易になる可能性があります。ただし、ホットスポットが目立たない場合は、実行できない可能性があります。

プロファイリングおよび速度が明確な要件であることを私たちに伝えるための+ 1、btw :)

22
Jon Skeet

問題は基本的にdouble/floatがハードウェアでサポートされているのに対し、Decimalなどはサポートされていないことです。つまり速度+精度の制限と精度の向上+パフォーマンスの低下のどちらかを選択する必要があります。

8
Brian Rasmussen

質問はよく議論されていますが、しばらくの間この問題を掘り下げていたので、私の結果のいくつかを共有したいと思います。

問題の定義:小数は倍精度よりもはるかに遅いことが知られていますが、金融アプリケーションは、倍精度で計算を実行したときに発生するアーティファクトを許容できません。

研究

私の目的は、浮動小数点数を格納するさまざまな方法を測定し、アプリケーションにどの方法を使用すべきかを結論付けることでした。

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

実験

私は次の操作を測定しました:加算、減算、乗算、除算、等値比較、および相対的(より大きい/少ない)比較。次のタイプの操作を測定していました:doublelongdecimalおよび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

結論

  1. decimalでの加算、減算、乗算、比較演算は、longまたはdoubleでの演算よりも最大15倍遅くなります。除算は約30倍遅くなります。
  2. DecimalのようなラッパーのパフォーマンスはDecimalのパフォーマンスよりも優れていますが、CLRからのサポートが不足しているため、doubleおよびlongのパフォーマンスよりも大幅に低下しています。
  3. Decimalに対して絶対数で計算を実行すると、1秒あたり40.000.000オペレーションと非常に高速になります。

アドバイス

  1. 非常に重い計算ケースがない限り、小数を使用してください。相対数ではlongやdoubleよりも低速ですが、絶対数は見栄えがします。
  2. CLRからのサポートが多すぎるため、独自の構造でDecimalを再実装する意味はあまりありません。 Decimalより高速にすることもできますが、doubleほど高速になることはありません。
  3. Decimalのパフォーマンスがアプリケーションにとって十分でない場合は、固定精度で計算をlongに切り替えることを検討してください。結果をクライアントに返す前に、Decimalに変換する必要があります。
8
user1921819

SSE2命令が.NET Decimal値で簡単に機能することはないと思います。 .NET Decimalデータ型は128ビット10進浮動小数点http://en.wikipedia.org/wiki/Decimal128_floating-point_format =、SSE2命令は128ビット整数型で動作します。

3
Sergey Shandar

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

古い質問ですが、それでも非常に有効です。

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
2
smirkingman

スタックオーバーフローを開始したばかりなので、コメントや投票はまだできません。 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では、固定精度ではなく、固定小数点に丸めることができることに注意してください。

更新:ここでコメントを追加できますが、コメント領域にこれを配置するための十分なスペースがありません。

1
ILoveFortran

私の4年後 前の回答 浮動小数点数を使用した高性能計算の操作に関する長年の経験に基づいて、もう1つ追加したいと思います。

高性能計算でのDecimalデータ型には2つの大きな問題があります。

  1. CLRはこの型を通常の構造体として扱います(他の組み込み型の場合のような特別なサポートはありません)
  2. で128ビット

最初の問題については多くのことを行うことはできませんが、2番目の問題はさらに重要に見えます。 64ビットの数値で操作する場合、メモリ操作とプロセッサは非常に効率的です。 128ビット操作ははるかに重いです。したがって、Decimalの.NET実装は、読み取り/書き込み操作であっても、Doubleでの操作よりも設計上大幅に遅くなります。

アプリケーションで浮動小数点計算の精度とそのような演算のパフォーマンスの両方が必要な場合、DoubleDecimalもタスクに適していません。私の会社(Fintechドメイン)で採用したソリューションは、 Intel®Decimal Floating-Point Math Library の上にラッパーを使用することです。 IEEE 754-2008 Decimal Floating-Point Arithmetic specification 64ビット浮動小数点10進数を提供します。

備考。 Decimalsは、浮動小数点数とそれらに対する単純な算術演算の格納にのみ使用してください。テクニカル分析のための指標の計算のようなすべての重い数学は、Double値に対して実行する必要があります。

0
user1921819

ダブルを使用して「ペニー」を格納します。入力の解析と出力の出力は別として、測定した速度と同じです。 64ビット整数の制限を克服します。あなたは切り捨てない部門を持っています注:除算後の二重結果の使用方法はあなた次第です。これはあなたの要件への最も簡単なアプローチのようです。

0
Massimo