programing

C # Decimal 데이터 형식 성능

sourcetip 2021. 1. 14. 23:45
반응형

C # Decimal 데이터 형식 성능


성능 (즉, 속도)이 중요한 C #으로 금융 애플리케이션을 작성하고 있습니다. 금융 앱이기 때문에 Decimal 데이터 유형을 집중적으로 사용해야합니다.

프로파일 러의 도움으로 최대한 코드를 최적화했습니다. Decimal을 사용하기 전에는 모든 것이 Double 데이터 유형으로 수행되었으며 속도가 몇 배 더 빨랐습니다. 그러나 Double은 바이너리 특성으로 인해 옵션이 아니므로 여러 작업 과정에서 많은 정밀도 오류가 발생합니다.

.NET의 기본 Decimal 데이터 유형에 비해 성능을 향상시킬 수있는 C #과 인터페이스 할 수있는 십진수 라이브러리가 있습니까?

이미 얻은 답변을 바탕으로 명확하지 않은 것으로 나타 났으므로 다음과 같은 추가 세부 정보가 있습니다.

  • 앱은 가능한 한 빨리 실행해야합니다 (즉, Decimal 대신 Double을 사용하는 것이 꿈이 될 때와 같은 속도). Double은 작업이 하드웨어 기반이므로 Decimal보다 약 15 배 더 빠릅니다.
  • 하드웨어는 이미 최고 수준이며 (저는 Dual Xenon Quad-Core에서 실행 중입니다) 응용 프로그램은 스레드를 사용하므로 CPU 사용률은 항상 컴퓨터에서 100 %입니다. 또한이 앱은 64 비트 모드로 실행되므로 32 비트에 비해 측정 가능한 성능 이점이 있습니다.
  • 나는 온전한 시점을 넘어 최적화했습니다 (최적화 한 달 반 이상, 믿거 나 말거나 처음에 참조로 사용한 것과 동일한 계산을 수행하는 데 걸린 작업의 약 1/5000이 걸립니다). 이 최적화에는 문자열 처리, I / O, 데이터베이스 액세스 및 인덱스, 메모리, 루프, 어떤 일이 만들어지는 방식 변경, 그리고 차이가있는 모든 곳에서 "if"대신 "switch"를 사용하는 등 모든 것이 포함되었습니다. 프로파일 러는 이제 나머지 성능 원인이 Decimal 데이터 유형 연산자에 있음을 분명히 보여줍니다. 다른 어떤 것도 상당한 시간을 합산하지 않습니다.
  • 여기에서 저를 믿어야합니다. 저는 응용 프로그램을 최적화하기 위해 C # .NET 영역에 들어갈 수있는 한 멀리까지 갔고 현재 성능에 정말 놀랐습니다. 이제 Decimal 성능을 Double에 가까운 것으로 개선하기위한 좋은 아이디어를 찾고 있습니다. 꿈일 뿐이라는 건 알지만 가능한 모든 생각을 확인하고 싶었습니다. :)

감사!


long 데이터 유형을 사용할 수 있습니다. 물론 거기에 분수를 저장할 수는 없지만 파운드 대신 페니를 저장하도록 앱을 코딩하면 괜찮을 것입니다. 긴 데이터 유형의 경우 정확도는 100 %이며 방대한 숫자 (64 비트 long 유형 사용)로 작업하지 않는 한 괜찮습니다.

동전을 저장하도록 요구할 수 없다면, 클래스에 정수를 래핑하고 사용하십시오.


빠른 속도가 필요하다고하는데 구체적인 속도 요구 사항이 있습니까? 그렇지 않은 경우 정상 지점을 넘어서 최적화 할 수 있습니다. :)

내 옆에 앉아있는 친구가 방금 제안했듯이 하드웨어를 업그레이드 할 수 있습니까? 이는 코드를 다시 작성하는 것보다 저렴할 것입니다.

가장 명백한 옵션은 소수 대신 정수를 사용하는 것입니다. 여기서 하나의 "단위"는 "천분의 일 센트"(또는 원하는대로-아이디어를 얻음)와 같은 것입니다. 그것이 실행 가능한지 여부는 시작할 십진수 값에 대해 수행하는 작업에 따라 다릅니다. 이 문제를 다룰 때 매우 조심 해야합니다. 실수하기 쉽습니다 (적어도 나와 같으면).

프로파일 러가 개별적으로 최적화 할 수있는 애플리케이션의 특정 핫스팟을 표시 했습니까? 예를 들어, 하나의 작은 코드 영역에서 많은 계산을 수행해야하는 경우 십진수에서 정수 형식으로 변환하고 계산을 수행 한 다음 다시 변환 할 수 있습니다. 이렇게 하면 대부분의 코드에 대해 소수 단위로 API 를 유지할 수 있으므로 유지 관리가 더 쉬워 질 수 있습니다. 그러나 발음 된 핫스팟이없는 경우 불가능할 수 있습니다.

프로파일 링 및 속도가 확실한 요구 사항임을 알려주는 +1, btw :)


문제는 기본적으로 하드웨어에서는 double / float가 지원되지만 Decimal 등은 지원되지 않는다는 것입니다. 즉, 속도 + 제한된 정밀도와 더 높은 정밀도 + 더 낮은 성능 중에서 선택해야합니다.


질문은 잘 논의되었지만 잠시 동안이 문제를 파고 있었으므로 결과 중 일부를 공유하고 싶습니다.

문제 정의 : 소수점은 두 배보다 훨씬 느린 것으로 알려져 있지만 금융 응용 프로그램은 두 배로 계산할 때 발생하는 인공물을 허용 할 수 없습니다.

연구

내 목표는 부동 소수점 숫자를 저장하는 다양한 접근 방식을 측정하고 우리 응용 프로그램에 사용해야하는 결론을 내리는 것이 었습니다.

Int64고정 정밀도로 부동 소수점 숫자를 저장하는 데 사용할 수 있다면 . 10 ^ 6의 승수는 우리 모두에게 분수를 저장하기에 충분한 자릿수를 제공하고 많은 양을 저장하기 위해 큰 범위를 유지했습니다. 물론이 접근 방식 (곱셈과 나눗셈 연산이 까다로울 수 있음)에주의해야하지만 우리는 준비가되어 있었고이 접근 방식도 측정하고 싶었습니다. 가능한 계산 오류 및 오버플로를 제외하고 명심해야 할 한 가지는 일반적으로 이러한 긴 숫자를 공용 API에 노출 할 수 없다는 것입니다. 따라서 모든 내부 계산은 long으로 수행 할 수 있지만 숫자를 사용자에게 보내기 전에 더 친숙한 것으로 변환해야합니다.

long 값을 십진수와 같은 구조 (라고 함 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, decimalMoney. 각 작업은 1.000.000 회 수행되었습니다. 모든 숫자는 지금의 생성자에서 사용자 지정 코드를 호출, 배열에 미리 할당 된 decimalMoney결과에 영향을 미치지 않습니다.

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. 더하기, 빼기, 곱하기, 비교 연산 decimallong또는 연산보다 ~ 15 배 느립니다 double. 분할은 ~ 30 배 느립니다.
  2. 성능 Decimal좋은 성능보다 래퍼 -like Decimal하지만 여전히 성능보다 훨씬 악화 doublelong때문에 CLR 지원의 부족.
  3. Decimal절대 숫자로 계산을 수행하는 것은 매우 빠릅니다 : 초당 40.000.000 작업.

조언

  1. 매우 무거운 계산 사례가 없다면 소수를 사용하십시오. 상대 수에서는 long 및 double보다 느리지 만 절대 수는 좋아 보입니다.
  2. DecimalCLR의 지원이 너무 많기 때문에 자체 구조로 다시 구현할 필요가 없습니다 . 당신은 그것을보다 더 빠르게 만들 수 Decimal있지만 결코 double.
  3. 의 성능이 Decimal응용 프로그램에 충분하지 않은 경우 계산을 long고정 정밀도 로 전환하는 것을 고려할 수 있습니다 . 결과를 클라이언트에 반환하기 전에 Decimal.

SSE2 명령이 .NET Decimal 값으로 쉽게 작동 할 수 있다고 생각하지 않습니다. .NET Decimal 데이터 유형은 128 비트 10 진수 부동 소수점 유형 http://en.wikipedia.org/wiki/Decimal128_floating-point_format , SSE2 명령어는 128 비트 정수 유형에서 작동 합니다 .


MMX / SSE / SSE2는 어떻습니까?

나는 그것이 도움이 될 것이라고 생각한다 ... 그래서 ... 십진수는 128 비트 데이터 유형이고 SSE2도 128 비트입니다 ... 그리고 1 CPU 틱에 sub, div, mul decimal을 추가 할 수 있습니다 ...

VC ++를 사용하여 SSE2 용 DLL을 작성한 다음 애플리케이션에서 해당 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);
}

Old question, still very valid though.

Here are some numbers to support the idea of using Long.

Time taken to perform 100'000'000 additions

Long     231 mS
Double   286 mS
Decimal 2010 mS

in a nutshell, decimal is ~10 times slower that Long or Double.

Code:

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

I cannot give a comment or vote down yet since I just started on stack overflow. My comment on alexsmart (posted 23 Dec 2008 12:31) is that the expression Round(n/precision, precision), where n is int and precisions is long will not do what he thinks:

1) n/precision will return an integer-division, i.e. it will already be rounded but you won't be able to use any decimals. The rounding behavior is also different from Math.Round(...).

2) The code "return Math.Round(n/precision, precision).ToString()" does not compile due to an ambiguity between Math.Round(double, int) and Math.Round(decimal, int). You will have to cast to decimal (not double since it is a financial app) and therefore can as well go with decimal in the first place.

3) n/precision, where precision is 4 will not truncate to four decimals but divide by 4. E.g., Math.Round( (decimal) (1234567/4), 4) returns 308641. (1234567/4 = 308641.75), while what you probably wanted to to is get 1235000 (rounded to a precision of 4 digits up from the trailing 567). Note that Math.Round allows to round to a fixed point, not a fixed precision.

Update: I can add comments now but there is not enough space to put this one into the comment area.


store "pennies" using double. apart from parsing input and printing outputs, you have the same speed you measured. you overcome the limit of 64 bit integer. you have a division not truncating. note : is up to you how to use the double result after divisions. this seems to me the simplest approach to your requirements.


4 years after my previous answer I would like to add another one based on the experience we had over the years on working with high-performance computations with floating-point numbers.

There are two major problems with Decimal data type on high-performance computations:

  1. CLR treats this type as a regular structure (no special support as for other built-in types)
  2. In is 128 bit

While you cannot do much about the first issue, second looks even more important. Memory operations and processors are extremely efficient when operating with 64-bit numbers. 128-bit operations are much heavier. Thus .NET implementation of Decimal is by design significantly slower that the operation on Double even for read/write operations.

If your application needs both the accuracy of floating-point computations and performance of such operations then neither Double or Decimal are suitable for the task. The solution that we have adopted in my company (Fintech domain) is to use a wrapper on top of Intel® Decimal Floating-Point Math Library. It implements the IEEE 754-2008 Decimal Floating-Point Arithmetic specification providing 64-bit floating-point decimals.

Remarks. Decimals should only be used for storing the floating-point numbers and simple arithmetic operations on them. All heavy mathematics like calculating indicators for technical analysis should be performed on Double values.

ReferenceURL : https://stackoverflow.com/questions/366852/c-sharp-decimal-datatype-performance

반응형