【问题标题】:How to improve performance and speed in my code (especially in double.Parse)?如何提高我的代码的性能和速度(尤其是在 double.Parse 中)?
【发布时间】:2014-02-06 23:04:35
【问题描述】:

我有 testFile.txt 文件(大约 400 毫克)。它包含时间范围为 1 分钟的 OHLC 股票价格。

它的结构:"股票名称、日期、时间、开盘价、最高价、最低价、收盘价、成交量"->"OTHE,20010102,230100,1.9007,1.9007,1.9007,1.9007,4" (这只是一个例子)。

我的主要问题 - 这段代码很慢。我测量了速度,发现关键部分是 double.Parse 部分。是否可以更改代码以提高性能? 我的c#解析代码:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Globalization;

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            string sourceDir = "D:\\testFile.txt",
                   outDir = "D:\\result.txt";
            Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;

            using (StreamReader sr = new StreamReader(sourceDir))
            {
                int divider = 5;
                string line = sr.ReadLine();
                StreamWriter sw = new StreamWriter(outDir);

                List<string> listLine = new List<string>();
                List<double> listOpen = new List<double>();
                List<double> listHigh = new List<double>();
                List<double> listLow = new List<double>();
                List<double> listClose = new List<double>();
                List<double> listVolume = new List<double>();
                DateTime dateTimeOut = new DateTime();
                string formatDate = "yyyyMMddHHmmss";
                string newLine = "";
                double priceOpen, priceHigh, priceLow, priceClose, volume;

                //read first line, but don't write it
                line = sr.ReadLine();

                while (line != null)
                {
                    listLine = line.Split(',').ToList();
                    dateTimeOut = DateTime.ParseExact(listLine[1] + listLine[2], formatDate, null);

                    double.TryParse(listLine[3], out priceOpen);
                    double.TryParse(listLine[4], out priceHigh);
                    double.TryParse(listLine[5], out priceLow);
                    double.TryParse(listLine[6], out priceClose);
                    double.TryParse(listLine[7], out volume);

                    listOpen.Add(priceOpen);
                    listHigh.Add(priceHigh);
                    listLow.Add(priceLow);
                    listClose.Add(priceClose);
                    listVolume.Add(volume);

                    if (dateTimeOut.Minute % divider == 0)
                    {
                        newLine = dateTimeOut + "," + listOpen[0] + "," + listHigh.Max() + "," + listLow.Min() + "," + listClose[4] + "," + listVolume.Max();
                        sw.WriteLine(newLine);
                    }
                    line = sr.ReadLine();
                }
                sr.Close();
            }
        }
    }
}

更新。问题出在这里:

                        if (dateTimeOut.Minute % divider == 0)
                        {
                            newLine = "";
                            sw.WriteLine(newLine);
                        }

【问题讨论】:

  • 您是根据数据实时显示或计算的吗?
  • 你能定义“非常慢”吗?你是如何测量速度的,在什么条件下?我注意到您忽略了double.TryParse 的返回值,顺便说一下-并且您使用double 来表示价格而不是decimal,这比性能更让我担心...
  • 哦,拥有 one 列表不是更明智,其类型包含单行的开盘/高/低/收盘/成交量值?
  • line.Split(',').ToList() 在性能关键代码中对我来说没有意义。 string.Split 返回一个数组,您已经可以使用该数组按索引访问字段。
  • @user3245303:在调试器下运行,还是不运行?这听起来比我预期的要慢得多,而且我怀疑你的方法是有缺陷的估计。那条采样线是真实的采样线吗? (所以我们可以执行类似的基准测试。)如果您可以提供一个简短但完整的程序用于基准测试,那将非常有帮助。

标签: c#


【解决方案1】:

我不认为Double.Parse() 是瓶颈。

我写了一个测试程序(如下所示)。发布版本在不到 20 秒的时间内解析了一亿个双精度:

using System;
using System.Diagnostics;

namespace Demo
{
    internal class Program
    {   
        private void run()
        {
            string s = "12345.6789";
            double result;
            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < 100000000; ++i)
                double.TryParse(s, out result);

            Console.WriteLine("Took " + sw.Elapsed);
        }

        private static void Main()
        {
            new Program().run();
        }
    }
}

【讨论】:

  • 试试我的代码并简单地评论 double.Parse!?瓶颈在哪里?
  • @user3245303 我不能试试你的,它不能编译!如果你能发布一个可编译的演示程序,那真的很有帮助。
  • )))) 为什么?你能解释一下吗?
  • 因为我认为您没有正确测试它,但是没有看到可运行的代码我无法确定。例如,您是否正在测试发布版本?
  • 为调试构建计时并不是那么有用...您应该为发布构建计时。
【解决方案2】:

您正在使用遍历整个集合的 LINQ Max() 和 Min() 函数。由于它们在循环中被调用数千次,并且集合包含数百万个元素,因此效率非常低。而是将最小值和最大值存储在循环之外,并在每次迭代时更新它们:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Globalization;

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            string sourceDir = "D:\\testFile.txt",
                   outDir = "D:\\result.txt";
            Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;

            using (StreamReader sr = new StreamReader(sourceDir))
            {
                int divider = 5;
                string line = sr.ReadLine();
                StreamWriter sw = new StreamWriter(outDir);

                List<string> listLine = new List<string>();
                List<double> listOpen = new List<double>();
                List<double> listHigh = new List<double>();
                List<double> listLow = new List<double>();
                List<double> listClose = new List<double>();
                List<double> listVolume = new List<double>();
                DateTime dateTimeOut = new DateTime();
                string formatDate = "yyyyMMddHHmmss";
                string newLine = "";
                double priceOpen, priceHigh, priceLow, priceClose, volume;

                //read first line, but don't write it
                line = sr.ReadLine();

                double highMax = double.MinValue;
                double lowMin = double.MaxValue;
                double volumeMax = double.MinValue;

                while (line != null)
                {
                    listLine = line.Split(',').ToList();
                    dateTimeOut = DateTime.ParseExact(listLine[1] + listLine[2], formatDate, null);

                    double.TryParse(listLine[3], out priceOpen);
                    double.TryParse(listLine[4], out priceHigh);
                    double.TryParse(listLine[5], out priceLow);
                    double.TryParse(listLine[6], out priceClose);
                    double.TryParse(listLine[7], out volume);

                    listOpen.Add(priceOpen);
                    listHigh.Add(priceHigh);
                    listLow.Add(priceLow);
                    listClose.Add(priceClose);
                    listVolume.Add(volume);

                    /*Here is implementation of accumulative max/min calculation*/
                    if (highMax < priceHigh)
                    {
                        highMax = priceHigh;
                    }

                    if (lowMin > priceLow)
                    {
                        lowMin = priceLow;
                    }

                    if (volumeMax < volume)
                    {
                        volumeMax = volume;
                    }

                    if (dateTimeOut.Minute % divider == 0)
                    {
                        newLine = dateTimeOut + "," + listOpen[0] + "," + highMax + "," + lowMin + "," + listClose[4] + "," + volumeMax;
                        sw.WriteLine(newLine);
                    }
                    line = sr.ReadLine();
                }
                sr.Close();
            }
        }
    }
}

在这种情况下,您甚至不需要将解析后的值添加到列表中(如果您没有它们的其他用途),因此您可以完全删除列表,进一步节省一些内存和时间。

【讨论】:

    【解决方案3】:

    double.Parse 非常慢,因为有很多方法可以表示双精度值:1000; 1000.1; 1e3、1.353e+34、-23.24e-123 等。 如果您只有一种预定义的格式(很可能您有),比如没有指数形式支持的 10394.324,那么您可以实现更高效的自定义解析器:从流中逐个字符读取,检查它是空格、数字还是点,然后累积结果或对结果进行相应处理。它实现起来相对简单,并且会提供更好的性能。如果您的硬盘允许读取速度如此之快,我想可以在不到 10 秒的时间内解析 400MB 文件 =)。

    另外,我不建议使用带有如此大量字符串的 string.Split - 它会消耗您的所有内存并经常发生垃圾收集,这可能会使您的代码速度减慢甚至超过 double.Parse。逐字节插入读取流。

    还有一点要提到的是 ToList() 创建新列表并将源集合的所有元素复制(引用)到其中。这也是非常耗时且耗费内存的不必要操作。

    最后,字符串连接不应该使用'+'操作符。

    所以我认为您的问题可能出在以下几行:

    line.Split(',').ToList();
    newLine = dateTimeOut + "," + listOpen[0] + "," + listHigh.Max() + "," + listLow.Min() + "," + listClose[4] + "," + listVolume.Max();
    

    如果运行你的程序消耗了所有的机器内存,那么 99% 的问题就在这里。

    尝试用少量后续调用 sw.Write(); 来替换第二行,以减轻“+”运算符并实现不需要字符串拆分的流式双解析器。

    【讨论】:

    • 但这并不是“很慢”——你可以在不到 20 秒的时间内解析 100,000,000 个 "12345.6789" 形式的字符串,这并不慢。
    • 听起来很有趣。我会尝试。但它只会给 10% stackoverflow.com/questions/8457934/…
    • 我不同意你不能得到超过 10%。您提供的链接使用相同的 double.Parse,只是带有特定的格式信息。好的实现可以在几次内加速解析。但我同意@MatthewWatson 的观点,即您的问题不在 double.Parse 中。他的代码看起来令人信服。
    • 在编译时字符串数量固定的情况下使用字符串连接根本不是问题。没有创建中间字符串,因为string.Concat 可以在需要连接任何值之前轻松计算最终字符串的大小。
    【解决方案4】:

    问题出在 List 上。我犯了愚蠢的错误。我几乎忘记了 List.Clear()。 ))) 所以,谢谢大家,尤其是 Oleksandr 和 Matthew。

    【讨论】:

      猜你喜欢
      • 2020-08-16
      • 2013-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多