【问题标题】:Fastest way to read rows form 100+ GB files fast in C# [duplicate]在 C# 中快速从 100+ GB 文件中读取行的最快方法 [重复]
【发布时间】:2018-12-02 02:25:12
【问题描述】:

处理将加载 100+ GB 文本文件的项目,其中一个过程是计算指定文件中的行数。我必须按照以下方式进行操作,以免出现内存不足异常。有没有更快的方法或者什么是多任务处理的最有效方法。 (我知道你可以做一些事情,比如在 4 个线程上运行它并将组合输出除以 4。不知道最有效的方法)

uint loadCount2 = 0;
foreach (var line in File.ReadLines(currentPath))
{
    loadCount2++;
}

当我修复了它的位置后,计划在具有 4 个双核 CPU 和 40 GB RAM 的服务器上运行该程序。目前,它运行在一个临时的小型 4 核 8GB RAM 服务器上。 (不知道线程在多个 CPU 上的表现如何。)


我测试了很多你的建议。

            Stopwatch sw2 = Stopwatch.StartNew();
            {
                using (FileStream fs = File.Open(json, FileMode.Open))
                    CountLinesMaybe(fs);
            }



            TimeSpan t = TimeSpan.FromMilliseconds(sw2.ElapsedMilliseconds);
            string answer = string.Format("{0:D2}h:{1:D2}m:{2:D2}s:{3:D3}ms", t.Hours, t.Minutes, t.Seconds, t.Milliseconds);
            Console.WriteLine(answer);
            sw2.Restart();
            loadCount2 = 0;


            Parallel.ForEach(File.ReadLines(json), (line) =>
            {
                loadCount2++;
            });


            t = TimeSpan.FromMilliseconds(sw2.ElapsedMilliseconds);
            answer = string.Format("{0:D2}h:{1:D2}m:{2:D2}s:{3:D3}ms", t.Hours, t.Minutes, t.Seconds, t.Milliseconds);
            Console.WriteLine(answer);
            sw2.Restart();
            loadCount2 = 0;

            foreach (var line in File.ReadLines(json))
            {
                loadCount2++;
            }

             t = TimeSpan.FromMilliseconds(sw2.ElapsedMilliseconds);
             answer = string.Format("{0:D2}h:{1:D2}m:{2:D2}s:{3:D3}ms", t.Hours, t.Minutes, t.Seconds, t.Milliseconds);
            Console.WriteLine(answer);
            sw2.Restart();
            loadCount2 = 0;

            int query = (int)Convert.ToByte('\n');
            using (var stream = File.OpenRead(json))
            {
                int current;
                do
                {
                    current = stream.ReadByte();
                    if (current == query)
                    {
                        loadCount2++;
                        continue;
                    }
                } while (current != -1);
            }

             t = TimeSpan.FromMilliseconds(sw2.ElapsedMilliseconds);
             answer = string.Format("{0:D2}h:{1:D2}m:{2:D2}s:{3:D3}ms", t.Hours, t.Minutes, t.Seconds, t.Milliseconds);
            Console.WriteLine(answer);
            Console.ReadKey();

    private const char CR = '\r';
    private const char LF = '\n';
    private const char NULL = (char)0;

    public static long CountLinesMaybe(Stream stream)
    {
        //Ensure.NotNull(stream, nameof(stream));

        var lineCount = 0L;

        var byteBuffer = new byte[1024 * 1024];
        const int BytesAtTheTime = 4;
        var detectedEOL = NULL;
        var currentChar = NULL;

        int bytesRead;
        while ((bytesRead = stream.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
        {
            var i = 0;
            for (; i <= bytesRead - BytesAtTheTime; i += BytesAtTheTime)
            {
                currentChar = (char)byteBuffer[i];

                if (detectedEOL != NULL)
                {
                    if (currentChar == detectedEOL) { lineCount++; }

                    currentChar = (char)byteBuffer[i + 1];
                    if (currentChar == detectedEOL) { lineCount++; }

                    currentChar = (char)byteBuffer[i + 2];
                    if (currentChar == detectedEOL) { lineCount++; }

                    currentChar = (char)byteBuffer[i + 3];
                    if (currentChar == detectedEOL) { lineCount++; }
                }
                else
                {
                    if (currentChar == LF || currentChar == CR)
                    {
                        detectedEOL = currentChar;
                        lineCount++;
                    }
                    i -= BytesAtTheTime - 1;
                }
            }

            for (; i < bytesRead; i++)
            {
                currentChar = (char)byteBuffer[i];

                if (detectedEOL != NULL)
                {
                    if (currentChar == detectedEOL) { lineCount++; }
                }
                else
                {
                    if (currentChar == LF || currentChar == CR)
                    {
                        detectedEOL = currentChar;
                        lineCount++;
                    }
                }
            }
        }

        if (currentChar != LF && currentChar != CR && currentChar != NULL)
        {
            lineCount++;
        }
        return lineCount;
    }

结果显示进步很大,但我希望达到 20 分钟。 我想让他们在我更强大的服务器上看到这个对拥有更多 CPU 的影响。

第二次运行返回: 23分钟, 25分钟, 22分钟, 29 分钟

意味着这些方法实际上并没有任何区别。 (无法截屏,因为我删除了暂停,程序通过清屏继续)

【问题讨论】:

  • 您认为文本文件中的“行”是什么?
  • 如果一行是一行,我认为你可以用 LINQ 计算\ns。
  • 另外,如果你创建了这些文件,那么如果创建一个单独的文件来存储行数,因为它不会在方法调用之间改变呢? (基本上是缓存数字)
  • 如果行数高于 Int.Max,调用 File.ReadLines("filename").Count() 可能会导致溢出。
  • 关于线程:获取线程数,相应地拆分可枚举(如果线程数过多,则限制线程数)并在每个线程上运行。但是当然,这应该只做一次,每次添加到该文件时,都应该在单独的文件中增加行数

标签: c# .net file-io


【解决方案1】:

基于 ReadByte(并与换行符比较)的方法可能比 ReadLine 更快。例如,对于更接近 GB 的文件

stopwatch = System.Diagnostics.Stopwatch.StartNew();
uint count = 0;
int query = (int)Convert.ToByte('\n');
using (var stream = File.OpenRead(filepath))
{
    int current;
    do
    {
        current = stream.ReadByte();
        if (current == query)
        {
            count++;
            continue;
        }
    } while (current!= -1);
}
Console.WriteLine($"Using ReadByte,Time : {stopwatch.Elapsed.TotalMilliseconds},Count: {r}");

使用ReadByte,时间:8174.5661,计数:7555107

stopwatch = System.Diagnostics.Stopwatch.StartNew();
uint loadCount2 = 0;
foreach (var line in File.ReadLines(filepath))
{
    loadCount2++;
}
Console.WriteLine($"Using ReadLines, Time : {stopwatch.Elapsed.TotalMilliseconds},Count: {r}");

使用 ReadLines,时间:27303.835,计数:7555107

【讨论】:

  • 忽略@theWongfonSemicolon 的完全错误评论,这实际上是合理的解决方案,只要 OP 可以保证文件不保存为 UTF16 (Unicode)、ASCII 或 UTF8 都可以。类似于stackoverflow.com/a/50508830/477420 中显示的一个(链接为副本),它更复杂,但具有相同的缺点。
【解决方案2】:

当您开始处理大数据时,您需要一个更强大的计算系统来让事情运行得更快。如果您想要速度,请增加 RAM 以将整个数据保存在内存中。添加 NVMe SSD 并将数据文件存储在其上以获得更快的读取性能。

软件方面,只需大块读取文件并遍历缓冲区检查每个字节计数换行符。您没有对添加或删除字符、检查模式等的文本行进行任何处理。ReadLine 在创建其数据结构以保持运行中的行时开销太大。

您不需要这种开销,而只需分配一次固定大小的大型缓冲区、读入数据并迭代寻找换行符。用 C 语言编写它也可以加快处理速度。

【讨论】:

    猜你喜欢
    • 2018-07-10
    • 2013-02-03
    • 2013-10-19
    • 2017-08-17
    • 1970-01-01
    • 2014-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多