C#十进制数据类型性能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/366852/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 00:43:15  来源:igfitidea点击:

C# Decimal datatype performance

c#performancedecimal

提问by tempw

I'm writing a financial application in C# where performance (i.e. speed) is critical. Because it's a financial app I have to use the Decimal datatype intensively.

我正在用 C# 编写一个财务应用程序,其中性能(即速度)至关重要。因为它是一个金融应用程序,所以我必须大量使用 Decimal 数据类型。

I've optimized the code as much as I could with the help of a profiler. Before using Decimal, everything was done with the Double datatype and the speed was several times faster. However, Double is not an option because of its binary nature, causing a lot of precision errors over the course of multiple operations.

我已经在分析器的帮助下尽可能地优化了代码。在使用 Decimal 之前,一切都是用 Double 数据类型完成的,速度要快几倍。但是,由于其二进制性质,Double 不是一种选择,在多次操作的过程中会导致很多精度错误。

Is there any decimal library that I can interface with C# that could give me a performance improvement over the native Decimal datatype in .NET?

是否有任何可以与 C# 交互的十进制库可以使我在 .NET 中的本机十进制数据类型的性能上有所改进?

Based on the answers I already got, I noticed I was not clear enough, so here are some additional details:

根据我已经得到的答案,我注意到我还不够清楚,所以这里有一些额外的细节:

  • The app has to be as fast as it can possibly go (i.e. as fast as it was when using Double instead of Decimal would be a dream). Double was about 15x faster than Decimal, as the operations are hardware based.
  • The hardware is already top-notch (I'm running on a Dual Xenon Quad-Core) and the application uses threads, so CPU utilization is always 100% on the machine. Additionally, the app is running in 64bit mode, which gives it a mensurable performance advantage over 32bit.
  • I've optimized past the point of sanity (more than one month and a half optimizing; believe it or not, it now takes approx. 1/5000 of what it took to do the same calculations I used as a reference initially); this optimization involved everything: string processing, I/O, database access and indexes, memory, loops, changing the way some things were made, and even using "switch" over "if" everywhere it made a difference. The profiler is now clearly showing that the remaining performance culprit is on the Decimal datatype operators. Nothing else is adding up a considerable amount of time.
  • You have to believe me here: I've gone as far as I could possibly go in the realm of C#.NET to optimize the application, and I'm really amazed at its current performance. I'm now looking for a good idea in order to improve Decimal performance to something close to Double. I know it's only a dream, but just wanted to check I thought of everything possible. :)
  • 该应用程序必须尽可能快(即使用 Double 而不是 Decimal 时的速度是梦想)。Double 比 Decimal 快约 15 倍,因为操作是基于硬件的。
  • 硬件已经是一流的(我在双氙气四核上运行)并且应用程序使用线程,因此机器上的 CPU 利用率始终为 100%。此外,该应用程序以 64 位模式运行,这使其比 32 位具有可衡量的性能优势。
  • 我已经优化过了理智点(优化了一个半月以上;不管你信不信,现在它只需要大约 1/5000 的时间来完成我最初用作参考的相同计算);这种优化涉及所有方面:字符串处理、I/O、数据库访问和索引、内存、循环、改变某些事情的制作方式,甚至在任何地方使用“切换”而不是“if”都会有所不同。分析器现在清楚地表明,剩下的性能罪魁祸首是 Decimal 数据类型运算符。没有其他任何事情会占用大量时间。
  • 在此您必须相信我:我已经在 C#.NET 领域尽可能地优化应用程序,我对其当前的性能感到非常惊讶。我现在正在寻找一个好主意,以将 Decimal 性能提高到接近 Double 的水平。我知道这只是一个梦,但只是想检查一下我想过的一切。:)

Thanks!

谢谢!

回答by Brian Rasmussen

The problem is basically that double/float are supported in hardware, while Decimal and the like are not. I.e. you have to choose between speed + limited precision and greater precision + poorer performance.

问题基本上是硬件支持双/浮点数,而小数等不支持。即您必须在速度 + 有限精度和更高的精度 + 较差的性能之间进行选择。

回答by Jon Skeet

You say it needs to be fast, but do you have concrete speed requirements? If not, you may well optimise past the point of sanity :)

你说它需要快,但你有具体的速度要求吗?如果没有,您可能会超出理智点进行优化:)

As a friend sitting next to me has just suggested, can you upgrade your hardware instead? That's likely to be cheaper than rewriting code.

正如坐在我旁边的朋友刚刚建议的那样,您可以升级您的硬件吗?这可能比重写代码便宜。

The most obvious option is to use integers instead of decimals - where one "unit" is something like "a thousandth of a cent" (or whatever you want - you get the idea). Whether that's feasible or not will depend on the operations you're performing on the decimal values to start with. You'll need to be verycareful when handling this - it's easy to make mistakes (at least if you're like me).

最明显的选择是使用整数而不是小数——其中一个“单位”类似于“千分之一”(或任何你想要的——你明白了)。这是否可行取决于您开始对十进制值执行的操作。在处理这个问题时你需要非常小心 - 很容易犯错误(至少如果你像我一样)。

Did the profiler show particular hotspots in your application that you could optimise individually? For instance, if you need to do a lot of calculations in one small area of code, you could convert from decimal to an integer format, do the calculations and then convert back. That could keep the APIin terms of decimals for the bulk of the code, which may well make it easier to maintain. However, if you don't have pronounced hotspots, that may not be feasible.

分析器是否在您的应用程序中显示了您可以单独优化的特定热点?例如,如果您需要在一小段代码中进行大量计算,则可以将十进制格式转换为整数格式,进行计算,然后再转换回来。对于大部分代码,这可以使API保持小数位,这很可能使其更易于维护。但是,如果您没有明显的热点,这可能不可行。

+1 for profiling and telling us that speed is a definite requirement, btw :)

+1 用于分析并告诉我们速度是一个明确的要求,顺便说一句:)

回答by gbjbaanb

you can use the long datatype. Sure, you won't be able to store fractions in there, but if you code your app to store pennies instead of pounds, you'll be ok. Accuracy is 100% for long datatypes, and unless you're working with vast numbers (use a 64-bit long type) you'll be ok.

您可以使用 long 数据类型。当然,您将无法在其中存储分数,但是如果您编写应用程序来存储便士而不是磅,那么您就可以了。长数据类型的准确性为 100%,除非您处理大量数据(使用 64 位长类型),否则您会没事的。

If you can't mandate storing pennies, then wrap an integer in a class and use that.

如果您不能强制存储便士,那么将一个整数包装在一个类中并使用它。

回答by ILoveFortran

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:

因为我刚刚开始堆栈溢出,所以我还不能发表评论或投反对票。我对 alexsmart 的评论(2008 年 12 月 23 日 12:31 发布)是表达式 Round(n/precision, precision),其中 n 是 int 且 precisions 是 long 不会像他想的那样:

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(...).

1) n/precision 将返回整数除法,即它已经四舍五入但您将无法使用任何小数。舍入行为也不同于 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.

2)由于 Math.Round(double, int) 和 Math.Round(decimal, int) 之间的歧义,代码“ return Math.Round(n/precision, precision).ToString()”无法编译。您将不得不转换为十进制(不是双精度,因为它是一个金融应用程序),因此也可以首先使用十进制。

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.

3) n/precision,其中精度为 4 不会截断为四位小数,而是除以 4。例如,Math.Round((decimal) (1234567/4), 4)返回 308641。(1234567/4 = 308641.75),而您可能想要的是得到 1235000(从尾随的 567 向上舍入到 4 位精度)。请注意, Math.Round 允许舍入到固定点,而不是固定精度。

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

更新:我现在可以添加评论了,但没有足够的空间将这个放入评论区。

回答by ILoveFortran

What about MMX/SSE/SSE2?

MMX/SSE/SSE2 怎么样?

i think it will help... so... decimal is 128bit datatype and SSE2 is 128bit too... and it can add, sub, div, mul decimal in 1 CPU tick...

我认为它会有所帮助......所以......十进制是128位数据类型,SSE2也是128位......它可以在1个CPU滴答中添加,sub,div,mul十进制......

you can write DLL for SSE2 using VC++ and then use that DLL in your application

您可以使用 VC++ 为 SSE2 编写 DLL,然后在您的应用程序中使用该 DLL

e.g //you can do something like this

例如//你可以做这样的事情

VC++

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#

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

回答by Sergey Shandar

I don't think that SSE2 instructions could easy work with .NET Decimal values. .NET Decimal data type is 128bit decimal floating pointtype http://en.wikipedia.org/wiki/Decimal128_floating-point_format, SSE2 instructions work with 128bit integer types.

我不认为 SSE2 指令可以轻松处理 .NET Decimal 值。.NET 十进制数据类型是128 位十进制浮点类型http://en.wikipedia.org/wiki/Decimal128_floating-point_format,SSE2 指令适用于128 位整数类型

回答by smirkingman

Old question, still very valid though.

老问题,但仍然非常有效。

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

这里有一些数字来支持使用 Long 的想法。

Time taken to perform 100'000'000 additions

执行 100'000'000 次加法所需的时间

Long     231 mS
Double   286 mS
Decimal 2010 mS

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

简而言之,decimal 比 Long 或 Double 慢约 10 倍。

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

回答by Massimo

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.

使用 double 存储“便士”。除了解析输入和打印输出之外,您的速度与您测量的速度相同。你克服了 64 位整数的限制。你有一个不截断的部门。注意:如何使用除法后的双重结果取决于您。在我看来,这似乎是满足您要求的最简单方法。

回答by user1921819

The question is well discussed but since I was digging this problem for a while I would like to share some of my results.

这个问题得到了很好的讨论,但由于我已经研究了一段时间这个问题,我想分享我的一些结果。

Problem definition:Decimals are known to be much slower than doubles but financial applications cannot tolerate any artefacts that arise when calculations are performed on doubles.

问题定义:众所周知,小数比双打慢得多,但金融应用程序不能容忍在双打上执行计算时出现的任何伪影。

Research

研究

My aim was to measure different approaches of storing float-pointing numbers and to make a conclusion which one should be used for our application.

我的目标是衡量存储浮点数的不同方法,并得出结论,应该在我们的应用程序中使用哪种方法。

If was acceptable for us to use Int64to store floating point numbers with fixed precision. Multiplier of 10^6 was giving us both: enough digits to store fractions and stil a big range to store large amounts. Of course, you have to be careful whith this approach (multiplication and division operations might become tricky), but we were ready and wanted to measure this approach as well. One thing you have to keep in mind except for possible calculation errors and overflows, is that usually you cannot expose those long numbers to public API. So all internal calculations could be performed with longs but before sending the numbers to the user they should be converted to something more friendly.

我们可以接受用于Int64存储具有固定精度的浮点数。10^6 的乘数给了我们两个:足够的数字来存储分数,并且仍然有一个很大的范围来存储大量的数字。当然,您必须小心使用这种方法(乘法和除法运算可能会变得棘手),但我们已经准备好并想测量这种方法。除了可能的计算错误和溢出之外,您必须记住的一件事是,通常您不能将这些长数字公开给公共 API。所以所有内部计算都可以用 long 执行,但在将数字发送给用户之前,它们应该转换为更友好的东西。

I've implemented a simple prototype class that wraps a long value to a decimal-like structure (called it Money) and added it to the measurments.

我已经实现了一个简单的原型类,它将一个长值包装成一个类似十进制的结构(称为它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);
    }
}

Experiment

实验

I measured following operations: addition, subtraction, multiplication, division, equality comparison and relative (greater/less) comparison. I was measuring operations on the following types: double, long, decimaland Money. Each operation was performed 1.000.000 times. All numbers were pre-allocated in arrays, so calling custom code in constructors of decimaland Moneyshould not affect the results.

我测量了以下操作:加法、减法、乘法、除法、相等比较和相对(大/小)比较。我测量操作以下类型:doublelongdecimalMoney。每个操作执行 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

Conclusions

结论

  1. Addition, subtraction, multiplication, comparison operations on decimalare ~15 times slower than operations on longor double; division is ~30 times slower.
  2. Performance of Decimal-like wrapper is better than performance of Decimalbut still significantly worse than performance of doubleand longdue to lack of support from CLR.
  3. Performing calculations on Decimalin absolute numbers is quite fast: 40.000.000 operations per second.
  1. 加法、减法、乘法、比较运算decimallongor 上的运算慢约 15 倍double;除法慢约 30 倍。
  2. 性能Decimal样的包装胜过的性能Decimal,但仍比性能显著恶化doublelong由于缺乏来自CLR的支持。
  3. Decimal以绝对数字执行计算非常快:每秒 40.000.000 次操作。

Advice

建议

  1. Unless you have a very heavy calculation case, use decimals. In relative numbers they are slower than longs and doubles, but absolute numbers look good.
  2. There is not much point in re-implementing Decimalwith your own structure due to abcense of support from CLR. You might make it faster than Decimalbut it will never be as fast as double.
  3. If performance of Decimalis not enough for your application, than you might want consider switching your calculations to longwith fixed precision. Before returning the result to the client it should be converted to Decimal.
  1. 除非你有一个非常繁重的计算案例,否则使用小数。在相对数字中,它们比 long 和 double 慢,但绝对数字看起来不错。
  2. Decimal由于缺乏 CLR 的支持,用您自己的结构重新实现没有多大意义。你可能会比它快,Decimal但它永远不会像double.
  3. 如果 的性能Decimal对于您的应用程序来说不够,那么您可能需要考虑将计算切换到long固定精度。在将结果返回给客户端之前,应将其转换为Decimal.

回答by user1921819

4 years after my previous answerI 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.

在我之前的回答4 年之后,我想根据我们多年来在使用浮点数进行高性能计算方面的经验再添加一个。

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

Decimal高性能计算中的数据类型有两个主要问题:

  1. CLR treats this type as a regular structure (no special support as for other built-in types)
  2. It is 128 bit
  1. CLR 将此类型视为常规结构(对其他内置类型没有特殊支持)
  2. 它是 128 位

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 Decimalis by design significantly slower that the operation on Doubleeven for read/write operations.

虽然您对第一个问题无能为力,但第二个问题看起来更为重要。内存操作和处理器在处理 64 位数字时非常高效。128 位操作要繁重得多。因此,.NET 的实现在Decimal设计上比Double读/写操作的操作慢得多。

If your application needs both the accuracy of floating-point computations and performance of such operations then neither Doubleor Decimalare 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 specificationproviding 64-bit floating-point decimals.

如果您的应用程序既需要浮点计算的准确性又需要此类操作的性能,那么两者都不适合DoubleDecimal不适合该任务。我们在我的公司(金融科技领域)采用的解决方案是在英特尔之上使用包装器十进制浮点数学库。它实现了IEEE 754-2008 Decimal Floating-Point Arithmetic specification提供 64 位浮点小数。

Remarks. Decimalsshould 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 Doublevalues.

评论。Decimals应该只用于存储浮点数和对它们的简单算术运算。所有繁重的数学运算,如计算技术分析的指标,都应该在Double数值上进行。