【问题标题】:Faster alternative to Convert.ToDouble(string)Convert.ToDouble(string) 的更快替代方案
【发布时间】:2012-01-17 11:31:11
【问题描述】:

有没有比Convert.ToDoublestring 转换为double更快 方法?

我已经监控了System.Convert.ToDouble(string) 的调用,它会降低我的应用性能。

Convert.ToDouble("1.34515");

来自 Jeffrey Sax 的工作答案:

static decimal[] decimalPowersOf10 = { 1m, 10m, 100m, 1000m, 10000m, 100000m, 1000000m }; 
static decimal CustomParseDecimal(string input) { 
    long n = 0; 
    int decimalPosition = input.Length; 
    for (int k = 0; k < input.Length; k++) { 
        char c = input[k]; 
        if (c == '.') 
            decimalPosition = k + 1; 
        else 
            n = (n * 10) + (int)(c - '0'); 
    } 
    return n / decimalPowersOf10[input.Length - decimalPosition]; 

}

【问题讨论】:

  • 嗯,显而易见的事情是避免首先需要转换(例如,首先将数据保持为双精度)。话虽如此,我也担心某些东西正在处理“价格”,但使用双精度数(即此类数字应首选十进制)
  • 转换有时会失败并抛出异常吗?异常是昂贵的(但你仍然可以每秒抛出几千个)。您多久拨打一次ToDouble?对ToDouble 的 2 次调用不可能花费 5 毫秒。你能给我们代码吗?为了衡量性能,经常执行它并计算平均值。
  • 我同意@Damien 的观点,货币值应该存储在decimal 中,而不是双倍,但这不会提高性能。
  • 一项快速测试表明,我的 comp 在单核上每秒可以进行大约 500 万次双精度解析。那么你解析了多少数据才能成为一个问题呢?
  • 请发布相关的代码子集。总时间的 50% 也是相关的。如果您的代码没有做任何昂贵的事情,那么 50% 也不算多。

标签: c# performance


【解决方案1】:

您可以通过调用Double.TryParse 并使用NumberStylesIFormatProvider 的特定缓存实例(即CultureInfo)节省大约10%:

var style = System.Globalization.NumberStyles.AllowDecimalPoint;
var culture = System.Globalization.CultureInfo.InvariantCulture;
double.TryParse("1.34515", style, culture, out x);

Convert.ToDoubleDouble.ParseDouble.TryParse 都必须假设输入可以是任何格式。如果您确定您的输入具有特定格式,则可以编写一个性能更好的自定义解析器。

这是转换为decimal 的一个。转换为double 类似。

static decimal CustomParseDecimal(string input) {
    long n = 0;
    int decimalPosition = input.Length;
    for (int k = 0; k < input.Length; k++) {
        char c = input[k];
        if (c == '.')
            decimalPosition = k + 1;
        else
            n = (n * 10) + (int)(c - '0');
    }
    return new decimal((int)n, (int)(n >> 32), 0, false, (byte)(input.Length - decimalPosition));
}

我的基准测试表明,这比 decimal 的原始速度快了大约 5 倍,如果您使用整数,则最多快 12 倍。

【讨论】:

  • 太棒了!谢谢,它确实提高了解析性能!
  • @StevenMuhr:很好奇,是什么提高了解析性能?拯救文化和风格?还是编写自己的自定义解析器?
  • @JimMischel:我正在使用 CustomParseDecimal。它真的提高了我的表现!
  • 请注意,此自定义解析器有一些限制:1) 它允许的有效数字较少,但对于大多数情况,64 位仍然足够 2) 它不支持符号 3) 它没有任何错误处理。
  • 你的函数很完美,但是如果你将 input.Length 缓存到一个变量中并使用它,那么它会显着加速。
【解决方案2】:

我无法重现此内容。这段代码测试Convert.ToDouble的速度。

        int numTests = 10000;
        double sum = 0;
        var sw = Stopwatch.StartNew();
        for (int i = 0; i < numTests; ++i)
        {
            var d = Convert.ToDouble("1.23456");
            sum += d;
        }
        sw.Stop();
        Console.WriteLine("{0} tests @ {1} ms. Avg of {2:N4} ms each", numTests,
           sw.ElapsedMilliseconds, (double)sw.ElapsedMilliseconds/numTests);
        Console.WriteLine("sum = {0}", sum);

有 10,000 个电话,我得到了

10000 tests @ 3 ms. Avg of 0.0003 ms each
sum = 12345.6000000021

这是在发布模式下,在没有附加调试器的情况下运行。

问题极不可能出在Convert.ToDouble

【讨论】:

  • 我还检查了调用没有被 JITter 优化掉。我创建了一个随机数字字符串数组并将它们用作参数。我也达到了类似的时间。
【解决方案3】:

您可以调用double.Parse("1.34515");,这是Convert.ToDouble 包装的。

调用double.TryParse 可能会更快,这将避免异常开销。

【讨论】:

    【解决方案4】:

    您可以尝试使用Double.Parse(String, NumberStyles, IFormatProvider) overload 减少对Thread.CurrentCulture 的调用次数。虽然我怀疑这会产生重大影响。

    解析到另一种类型:floatdecimal 可能会赢几个百分点。

    有点疯狂的想法,但是...您可以缓存NumberFormatInfo 实例并使用反射直接调用内部System.Number.ParseDouble。这会减少对NumberFormatInfo.GetInstance() 的调用次数,但老实说,我预计反射会慢得多。

    剩下的唯一选择(除了避免解析)是使用一些自定义解析方法。例如,如果您为数字定义了严格的格式(例如#.####),您最终可能会得到一个更快但不太灵活和/或安全的实现。但考虑到内置解析是半原生的,我怀疑你会赢。

    更新

    我对 .NET 代码进行了更多分析,发现NumberFormatInfoIFormatProvider。所以看起来最快的代码应该是:

    IFormatProvider _CachedProvider = NumberFormatInfo.CurrentInfo;
    var value1 = double.Parse(str1, NumberStyles.Number, _CachedProvider);
    var value2 = double.Parse(str2, NumberStyles.Number, _CachedProvider);
    

    此代码应尽可能减少解析准备所花费的时间。如果您解析大量字符串对,您还可以将 IFormatProvider 缓存提取到(可能)运行循环并赢得另外几毫秒的外部代码。

    【讨论】:

      【解决方案5】:

      本文Faster alternative to decimal.Parse 中的功能基于Jeffrey Sax 的代码。它增加了对负数的支持,通过将 input.Length 缓存到变量中来优化性能,并且也适用于更大的数字。

      【讨论】:

        【解决方案6】:

        double.Parse() 应该快一点。

        【讨论】:

        • @StevenMuhr,我觉得这很奇怪,因为 Convert.ToDouble 在内部使用 double.Parse。
        • 我用 Parse 重新运行了一些测试,我有 5 毫秒。不幸的是,Convert.ToDouble 和 Double.Parse 之间的区别真的很小
        • double.Parse() 更快。见stackoverflow.com/questions/586436/…
        【解决方案7】:

        如果您 100% 确定您的源数据格式范围,您可以使用:

        string num = "1.34515";
        int len = num.Length - num.IndexOf('.') - 1;
        int intval = Int32.Parse(num.Replace(".", ""));
        double d = (double)intval / PowersOf10[len]; // PowersOf10 is pre-computed or inlined
        

        对我来说,它的工作速度比 Double.Parse 快约 50%,但我不会在任何严肃的应用程序中使用它 - 与正确解析相比,它非常有限,我想不出你需要解析数百万个的过程双倍和几毫秒会有所不同。

        【讨论】:

          猜你喜欢
          • 2018-12-11
          • 2012-07-05
          • 2012-01-23
          • 2013-07-13
          • 2011-02-27
          • 2010-09-22
          • 2017-05-15
          • 1970-01-01
          相关资源
          最近更新 更多