【问题标题】:How to parse a text file in C# and be io bound?如何在 C# 中解析文本文件并进行 io 绑定?
【发布时间】:2011-08-22 20:37:36
【问题描述】:

众所周知,如果您从磁盘读取数据,您会受到 IO 限制,并且您可以比从磁盘读取数据更快地处理/解析读取的数据。

但是我的测试并没有反映这种普遍的智慧(神话?)。当我读取一个文本文件时,每行中用空格分隔的 double 和 int 比我的物理磁盘速度慢得多(因子 6)。 文本文件如下所示

1,1 0
2,1 1
3,1 2

更新 当我在一次读取中使用完整缓冲区执行 ReadFile 以获得“真实”性能时,我已经包含了 PInvoke 性能。

  • ReadFile 性能 - ReadFileIntoByteBuffer
  • StringReader.ReadLine 性能 - CountLines
  • StringReader.Readline 不安全性能 - ParseLinesUnsafe
  • StringReader.Read unsafe char buf - ParseLinesUnsafeCharBuf
  • StringReader.ReadLine + 解析性能 - ParseLines

结果

Did native read 179,0MB in                    0,4s, 484,2MB/s
Did read 10.000.000 lines in                  1,6s, 112,7MB/s
Did parse and read unsafe 179,0MB in          2,3s,  76,5MB/s
Did parse and read unsafe char buf 179,0MB in 2,8s,  63,5MB/s
Did read and parse 179,0MB in                 9,3s,  19,3MB/s

虽然我确实尝试跳过 ParseLinesUnsafeCharBuf 中的字符串构造开销,但它仍然比每次分配新字符串的版本慢很多。它仍然比最初的 20 MB 和最简单的解决方案要好得多,但我确实认为 .NET 应该能够做得更好。如果去掉解析字符串的逻辑,我确实得到 258,8 MB/s,这非常好,接近原生速度。但是我看不到使用不安全代码使我的解析更简单的方法。我确实必须处理不完整的行,这使得它非常复杂。

更新 从数字中可以清楚地看出,一个简单的 string.split 的成本已经太高了。但是 StringReader 也确实要花很多钱。高度优化的解决方案如何看起来更接近真实的磁盘速度?我已经尝试了许多使用不安全代码和字符缓冲区的方法,但性能提升可能是 30%,但没有我需要的数量级。我会接受 100MB/s 的解析速度。这应该可以通过托管代码实现,还是我错了?

C# 的解析速度是否比我从硬盘读取的速度更快?它是英特尔 Postville X25M。 CPU 是和旧的英特尔双核。我有 3 GB RAM Windows 7 .NET 3.5 SP1 和 .NET 4。

但我确实在普通硬盘上也看到了相同的结果。当今硬盘的线性读取速度可高达 400MB/s。这是否意味着我应该重构我的应用程序以在实际需要时按需读取数据,而不是因为增加的对象图使 GC 周期更长而以更高的 GC 时间为代价急切地将其读取到内存中。

I have noticed 如果我的托管应用程序使用超过 500MB 的内存,它的响应速度就会大大降低。一个主要的影响因素似乎是对象图的复杂性。因此,在需要时读取数据可能会更好。至少这是我对当前数据的结论。

这里是代码

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;

namespace IOBound
{
    class Program
    {
        static void Main(string[] args)
        {
            string data = @"C:\Source\IOBound\NumericData.txt";
            if (!File.Exists(data))
            {
                CreateTestData(data);
            }

            int MB = (int) (new FileInfo(data).Length/(1024*1024));

            var sw = Stopwatch.StartNew();
            uint bytes = ReadFileIntoByteBuffer(data);
            sw.Stop();
            Console.WriteLine("Did native read {0:F1}MB in {1:F1}s, {2:F1}MB/s",
                bytes/(1024*1024), sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);

            sw = Stopwatch.StartNew();
            int n = CountLines(data);
            sw.Stop();
            Console.WriteLine("Did read {0:N0} lines in {1:F1}s, {2:F1}MB/s",
                n, sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);

            sw = Stopwatch.StartNew();
            ParseLinesUnsafe(data);
            sw.Stop();
            Console.WriteLine("Did parse and read unsafe {0:F1}MB in {1:F1}s, {2:F1}MB/s",
                MB, sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);

            sw = Stopwatch.StartNew();
            ParseLinesUnsafeCharBuf(data);
            sw.Stop();
            Console.WriteLine("Did parse and read unsafe char buf {0:F1}MB in {1:F1}s, {2:F1}MB/s",
                MB, sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);

            sw = Stopwatch.StartNew();
            ParseLines(data);
            sw.Stop();
            Console.WriteLine("Did read and parse {0:F1}MB in {1:F1}s, {2:F1}MB/s",
                MB, sw.Elapsed.TotalSeconds, MB / sw.Elapsed.TotalSeconds);

        }

        private unsafe static uint ReadFileIntoByteBuffer(string data)
        {
            using(var stream = new FileStream(data, FileMode.Open))
            {
                byte[] buf = new byte[200 * 1024 * 1024];
                fixed(byte* pBuf = &buf[0])
                {
                    uint dwRead = 0;
                    if (ReadFile(stream.SafeFileHandle, pBuf, 200 * 1000 * 1000, out dwRead, IntPtr.Zero) == 0)
                    {
                        throw new Win32Exception();
                    }
                    return dwRead;
                }

            }
        }

        private static int CountLines(string data)
        {
            using (var reader = new StreamReader(data))
            {
                string line;
                int count = 0;
                while ((line = reader.ReadLine()) != null)
                {
                    count++;
                }

                return count;
            }
        }

        unsafe private static void ParseLinesUnsafeCharBuf(string data)
        {
            var dobules = new List<double>();
            var ints = new List<int>();

            using (var reader = new StreamReader(data))
            {
                double d = 0;
                long a = 0, b = 0;
                int i = 0;
                char[] buffer = new char[10*1000*1000];
                int readChars = 0;
                int startIdx = 0;

                fixed(char *ln = buffer)
                {
                    while ((readChars = reader.Read(buffer, startIdx, buffer.Length - startIdx)) != 0)
                    {
                        char* pEnd = ln + readChars + startIdx;
                        char* pCur = ln;
                        char* pLineStart = null;

                        while (pCur != pEnd)
                        {
                            a = 0;
                            b = 0;

                            while (pCur != pEnd && *pCur == '\r' || *pCur == '\n')
                            {
                                pCur++;
                            }
                            pLineStart = pCur;

                            while(pCur != pEnd && char.IsNumber(*pCur))
                            {
                                a = a * 10 + (*pCur++ - '0');
                            }
                            if (pCur == pEnd || *pCur == '\r')
                            {
                                goto incompleteLine;
                            }

                            if (*pCur++ == ',')
                            {
                                long div = 1;
                                while (pCur != pEnd && char.IsNumber(*pCur))
                                {
                                    b += b * 10 + (*pCur++ - '0');
                                    div *= 10;
                                }
                                if (pCur == pEnd || *pCur == '\r')
                                {
                                    goto incompleteLine;
                                }
                                d = a + ((double)b) / div;
                            }
                            else
                            {
                                goto skipRest;
                            }

                            while (pCur != pEnd && char.IsWhiteSpace(*pCur))
                            {
                                pCur++;
                            }
                            if (pCur == pEnd || *pCur == '\r')
                            {
                                goto incompleteLine;
                            }

                            i = 0;
                            while (pCur != pEnd && char.IsNumber(*pCur))
                            {
                                i = i * 10 + (*pCur++ - '0');
                            }
                            if (pCur == pEnd)
                            {
                                goto incompleteLine;
                            }

                            dobules.Add(d);
                            ints.Add(i);

                            continue;

incompleteLine:
                            startIdx = (int)(pEnd - pLineStart);
                            Buffer.BlockCopy(buffer, (int)(pLineStart - ln) * 2, buffer, 0, 2 * startIdx);
                            break;
skipRest:
                            while (pCur != pEnd && *pCur != '\r')
                            {
                                pCur++;   
                            }
                            continue;
                        }
                    }
                }
            }
        }

        unsafe private static void ParseLinesUnsafe(string data)
        {
            var dobules = new List<double>();
            var ints = new List<int>();

            using (var reader = new StreamReader(data))
            {
                string line;
                double d=0;
                long a = 0, b = 0;
                int ix = 0;
                while ((line = reader.ReadLine()) != null)
                {
                    int len = line.Length;
                    fixed (char* ln = line)
                    {
                        while (ix < len && char.IsNumber(ln[ix]))
                        { 
                            a = a * 10 + (ln[ix++] - '0');
                        }

                        if (ln[ix] == ',')
                        {
                            ix++;
                            long div = 1;
                            while (ix < len && char.IsNumber(ln[ix]))
                            {
                                b += b * 10 + (ln[ix++] - '0');
                                div *= 10;
                            }
                            d = a + ((double)b) / div;
                        }

                        while (ix < len && char.IsWhiteSpace(ln[ix]))
                        {
                            ix++;
                        }

                        int i = 0;
                        while (ix < len && char.IsNumber(ln[ix]))
                        { 
                            i = i * 10 + (ln[ix++] - '0');
                        }

                        dobules.Add(d);
                        ints.Add(ix);
                    }
                }
            }
        }



        private static void ParseLines(string data)
        {
            var dobules = new List<double>();
            var ints = new List<int>();

            using (var reader = new StreamReader(data))
            {
                string line;
                char[] sep  = new char[] { ' ' };
                while ((line = reader.ReadLine()) != null)
                {
                    var parts = line.Split(sep);
                    if (parts.Length == 2)
                    {
                        dobules.Add( double.Parse(parts[0]));
                        ints.Add( int.Parse(parts[1]));
                    }
                }
            }
        }

        static void CreateTestData(string fileName)
        {
            FileStream fstream = new FileStream(fileName, FileMode.Create);
            using (StreamWriter writer = new StreamWriter(fstream, Encoding.UTF8))
            {
                for (int i = 0; i < 10 * 1000 * 1000; i++)
                {
                    writer.WriteLine("{0} {1}", 1.1d + i, i);
                }
            }
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        unsafe static extern uint ReadFile(SafeFileHandle hFile, [Out] byte* lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);

    }
}

【问题讨论】:

  • “常识”确实被广泛接受,甚至更广泛地是错误的。磁盘吞吐量已经变得相当高,即使您没有足够幸运拥有 SATA-3 SSD,您也可以将 RAM 用作磁盘缓存。这也不是 C# 问题,例如,请参阅 stackoverflow.com/questions/4340396 stackoverflow.com/questions/6218667stackoverflow.com/questions/5678932 当然,一些加速处理的技术(将磁盘访问作为限制因素)在托管代码中更加困难。
  • 我认为明确提及您的硬盘是 SSD 是个好主意 ;-)
  • 底线:可以比现代 I/O 子系统更快地处理数据(也不如基于 RAM 的磁盘缓存快)。但是您需要非常注重性能才能这样做,并且不要使用通用库函数。
  • @Meta-Knight:问题中提到的 400MB/s 必须是 SSD 或磁盘阵列。但结果中的 120MB/s,仅用于读取,如今使用高端消费级旋转磁盘可以实现。
  • 我很困惑是什么让你相信第二次测试应该和第一次测试一样快。我假设您认为 I/O 是循环中如此巨大的组成部分,以至于解析几乎可以忽略不计?如果是这样,你假设这个的理由是什么?当然,“传统智慧”对于这种测试来说是非常模糊的。有很多需要考虑。

标签: c# performance file-io


【解决方案1】:

所以这里有几个问题。其他人已经评论了 Windows 的 IO 缓存以及实际的硬件缓存,所以我将不理会它。

另一个问题是您测量 read() + parse() 的组合操作,并将其与 read() 的速度进行比较。从本质上讲,您需要意识到 A + B 将始终大于 A(假设非负数)。

因此,要确定您是否受 IO 限制,您需要了解读取文件需要多长时间。你已经做到了。在我的机器上,您的测试以大约 220 毫秒的时间运行以读取文件。

现在您需要测量解析这么多不同的字符串需要多长时间。这有点难以隔离。因此,假设我们将它们放在一起,并从解析时间中减去读取时间。此外,我们并没有试图衡量你对数据做了什么,而只是分析,所以扔掉 List 和 List,让我们来分析一下。在我的机器上运行它大约需要 1000 毫秒,减去 220 毫秒的读取时间,您的解析代码每 100 万行大约需要 780 毫秒。

那么为什么它这么慢(比读取慢 3-4 倍)?再次让我们消除一些东西。注释掉 int.Parse 和 double.Parse 并再次运行。这比 220 的读取时间少 460 毫秒要好得多,我们现在的解析时间为 240 毫秒。当然,“解析”只是调用 string.Split()。 Hrmmm 看起来像 string.Split 将花费您与磁盘 IO 一样多的成本,考虑到 .NET 如何处理字符串也就不足为奇了。

那么 C# 的解析速度是否可以比从磁盘读取的速度更快?好吧,是的,它可以,但你将不得不变得讨厌。您会看到 int.Parse 和 double.Parse 受到文化意识这一事实的影响。由于这一点以及这些解析例程处理许多格式的事实,它们在您的示例的数量级上有些昂贵。我的意思是说我们每微秒(百万分之一秒)解析一个 double 和 int,这通常还不错。

因此,为了匹配磁盘读取的速度(因此受 IO 限制),我们需要重写处理文本行的方式。这是一个讨厌的例子,但它适用于你的例子......

int len = line.Length;
fixed (char* ln = line)
{
    double d;
    long a = 0, b = 0;
    int ix = 0;
    while (ix < len && char.IsNumber(ln[ix]))
        a = a * 10 + (ln[ix++] - '0');
    if (ln[ix] == '.')
    {
        ix++;
        long div = 1;
        while (ix < len && char.IsNumber(ln[ix]))
        {
            b += b * 10 + (ln[ix++] - '0');
            div *= 10;
        }
        d = a + ((double)b)/div;
    }

    while (ix < len && char.IsWhiteSpace(ln[ix]))
        ix++;

    int i = 0;
    while (ix < len && char.IsNumber(ln[ix]))
        i = i * 10 + (ln[ix++] - '0');
}

运行这个糟糕的代码会产生大约 450 毫秒的运行时间,或大约 2n 的读取时间。所以,假装你认为上面的代码片段是可以接受的(我希望你不要这样),你可以让一个线程读取字符串和另一个解析,你将接近 IO 绑定。将两个线程放在解析中,您将受到 IO 限制。 应该你这样做是另一个问题。

让我们回到你原来的问题:

众所周知,如果您从磁盘读取数据,您会受到 IO 限制,并且您可以比从磁盘读取数据更快地处理/解析读取的数据。

但是这种普遍的智慧(神话?)

不,我不会把这称为神话。事实上,我会争论你的原始代码仍然是 IO Bound。您碰巧单独运行您的测试,因此影响很小,只有从设备读取所用时间的 1/6。但是考虑一下如果那个磁盘很忙会发生什么?如果您的防病毒扫描程序正在翻阅每个文件怎么办?简单地说,随着 HDD 活动的增加,您的程序会变慢,并且可能会成为 IO Bound。

恕我直言,这种“共同智慧”的原因是这样的:

在写入时绑定 IO 比在读取时更容易。

写入设备需要更长的时间,并且通常比生成数据更昂贵。如果您想查看 IO Bound 的实际效果,请查看您的“CreateTestData”方法。您的 CreateTestData 方法将数据写入磁盘所需的时间是调用 String.Format(...) 的 2 倍。这是完全缓存。关闭缓存 (FileOptions.WriteThrough) 并再试一次……现在 CreateTestData 慢了 3 到 4 倍。自己试试以下方法:

static int CreateTestData(string fileName)
{
    FileStream fstream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough);
    using (StreamWriter writer = new StreamWriter(fstream, Encoding.UTF8))
    {
        for (int i = 0; i < linecount; i++)
        {
            writer.WriteLine("{0} {1}", 1.1d + i, i);
        }
    }
    return linecount;
}
static int PrintTestData(string fileName)
{
    for (int i = 0; i < linecount; i++)
    {
        String.Format("{0} {1}", 1.1d + i, i);
    }
    return linecount;
}

这只是为了初学者,如果你真的想获得 IO 绑定,你可以开始使用直接 IO。请参阅使用 FILE_FLAG_NO_BUFFERING 的 CreateFile 文档。当您开始绕过硬件缓存并等待 IO 完成时,写入会变得更慢。这是传统数据库写入速度非常慢的主要原因之一。他们必须强制硬件完成写入并等待它。只有这样他们才能将事务称为“已提交”,数据在物理设备上的文件中。

更新

好吧,Alois,看来您只是在寻找自己能跑多快。为了更快,您需要停止处理字符串和字符并删除分配以更快。下面的代码对上面的行/字符解析器进行了大约一个数量级的改进(在仅计算行的基础上增加了大约 30 毫秒),同时仅在堆上分配了一个缓冲区。

警告您需要意识到我正在证明它可以快速完成。我建议你走这条路。此代码有一些严重的限制和/或错误。比如当你击中“1.2589E+19”形式的双倍时会发生什么?坦率地说,我认为您应该坚持使用您的原始代码,而不必担心尝试优化它。要么将文件格式更改为二进制而不是文本(请参阅BinaryWriter)。如果您使用二进制,您可以使用以下代码的变体与BitConvert.ToDouble/ToInt32,它会更快。

private static unsafe int ParseFast(string data)
{
    int count = 0, valid = 0, pos, stop, temp;
    byte[] buffer = new byte[ushort.MaxValue];

    const byte Zero = (byte) '0';
    const byte Nine = (byte) '9';
    const byte Dot = (byte)'.';
    const byte Space = (byte)' ';
    const byte Tab = (byte) '\t';
    const byte Line = (byte) '\n';

    fixed (byte *ptr = buffer)
    using (Stream reader = File.OpenRead(data))
    {
        while (0 != (temp = reader.Read(buffer, valid, buffer.Length - valid)))
        {
            valid += temp;
            pos = 0;
            stop = Math.Min(buffer.Length - 1024, valid);
            while (pos < stop)
            {
                double d;
                long a = 0, b = 0;
                while (pos < valid && ptr[pos] >= Zero && ptr[pos] <= Nine)
                    a = a*10 + (ptr[pos++] - Zero);
                if (ptr[pos] == Dot)
                {
                    pos++;
                    long div = 1;
                    while (pos < valid && ptr[pos] >= Zero && ptr[pos] <= Nine)
                    {
                        b += b*10 + (ptr[pos++] - Zero);
                        div *= 10;
                    }
                    d = a + ((double) b)/div;
                }
                else
                    d = a;

                while (pos < valid && (ptr[pos] == Space || ptr[pos] == Tab))
                    pos++;

                int i = 0;
                while (pos < valid && ptr[pos] >= Zero && ptr[pos] <= Nine)
                    i = i*10 + (ptr[pos++] - Zero);

                DoSomething(d, i);

                while (pos < stop && ptr[pos] != Line)
                    pos++;
                while (pos < stop && !(ptr[pos] >= Zero && ptr[pos] <= Nine))
                    pos++;
            }

            if (pos < valid)
                Buffer.BlockCopy(buffer, pos, buffer, 0, valid - pos);
            valid -= pos;
        }
    }
    return count;
}

【讨论】:

  • +1 是用两个 long 解析双精度的好主意。总有一天会派上用场的。该代码对我来说看起来不错且可读。我习惯了 C++
  • 使用您的解决方案,我得到了 80 MB/s,这是一个相当大的进步。然后我确实切换到 Read 以使用 char 缓冲区。但是使用这个解决方案,我确实只能得到大约 60MB/s。带有指向字符的指针的不安全代码似乎比高度优化的字符串解决方案慢得多。很遗憾,因为字符串分配肯定是提高速度的限制因素,但如果非分配解决方案变得更慢,我将没有进一步优化的选项
  • @Alois Kraus,查看更新以获得更多优化。阅读警告;)
  • 是的,我确实阅读了警告。不,我不会极端牺牲可读性。我想知道一个人能跑多快。在我的机器上,我确实获得了 90 MB/s,但您的代码确实忽略了将其标记为 UTF-8 文件的 BOM(开头为 3 个字节),并且您确实读取了超过 1000 万个数字。与第一个解决方案相比,速度提升令人印象深刻。也许我可以使用 char 缓冲池来使用多个内核,而无需为每个新行分配一个新对象。
【解决方案2】:

正如其他答案/cmets 所提到的,您可能正在从缓存中读取文件,因此磁盘/SDRAM 速度不是限制因素。

当你解析文件时,你从堆中分配更多的内存(当你分割字符串时,当你将自动装箱的解析值添加到列表中时)比只计算行数时要多得多。正是这些堆分配的成本可能会导致两次传递之间的性能差异。

可以解析比从磁盘读取的速度更快,但针对速度优化的解析器会注意内存使用情况。

【讨论】:

  • 这样的解析器会是什么样子?这正是我正在寻找的答案。到目前为止,我无法使用托管代码来接近 100MB/s 的解析速度。
【解决方案3】:

只是几个建议(如果您愿意接受更复杂的实现)...

  • 您可以使用LinkedList 而不是List 以避免在Add-ing 时重新分配/复制。
  • Split 替换为搜索分隔符然后使用Substring 提取“字段”的手写代码。您只需要搜索单个分隔符,并且预先知道字段数,因此Split 对您来说太笼统了。
  • 使用Read 而不是ReadLine,这样您就可以重复使用相同的缓冲区并避免为每一行分配新字符串。
  • 如果您真的注重性能,请单独解析为并发Tasks。当你在做的时候,把文件读取也放到它自己的任务中。

【讨论】:

  • +1 建议使用 Read 而不是 Readline。这可以节省一些对象分配。我知道线程可以提供帮助,但首先我想尽可能快地使用单线程。
【解决方案4】:

关于性能,只有一个“常识”——决定你希望处理的速度有多快,测量所有内容,根据收集到的数据采取行动并重复。

到目前为止,您的代码可能会为每个字节读取分配 10 倍的内存(有很多层的读取器、字符串读取器等)。如果您想要绝对速度,您可能需要消除大部分重新分配。我会首先重写代码以一直使用单个阅读器并衡量性能是否足够好。

【讨论】:

    【解决方案5】:

    如果您使用大文件,最快的方法是 AFAIK MemoryMappedFile 类(.NET 4 中的新功能)。

    通过这种方式,您基本上可以直接使用 OS FileSystem 缓存管理器内存页面...如果这还不够快,那么您需要编写自己的文件系统...

    对于 GC,可以通过 app.config 自定义行为 - 例如:

    <runtime>
       <gcServer enabled="true" />
    </runtime>
    

    有关 GC 自定义选项,请参阅http://msdn.microsoft.com/en-us/library/6bs4szyc.aspx - 尤其是。 &lt;gcConcurrent&gt; / &lt;gcServer&gt; .

    【讨论】:

    • 问题不是让 IO 更快,而是恰恰相反。 .NET 的抽象使我不受 IO 限制。新的文件系统在这里无济于事。
    • 这不是一个新的文件系统 - 它是您在 .NET 框架中找到的最快的内置文件访问......另一点是处理这么多字符串时 GC 的相互引用 - 因此“GC 自定义设置”...
    • 我的问题确实表明我目前的方法不受 IO 限制。所以不需要在IO端优化,而是在托管端解析文件。如果解析器可以处理 20 MB/s 但您可以通过 400MB/s 的内存映射文件进行读取,则内存映射文件没有帮助。如果您有超过 2 个核心,如文档所述,GC 设置会有所帮助,但如果您避免对象分配,例如,您将获得更多。通过 char 缓冲池在另一个线程上进行解析并将使用的 char 缓冲区放回池中。
    【解决方案6】:

    使用分析器,看起来大部分时间确实花在解析文件上。

    我尝试使用已读取的byte[] 而不是ParseLines 方法的路径传入MemoryStream 的示例,并且从文件路径解析和从内存字节解析之间的差异可以忽略不计。

    换句话说,是完成的处理,而不是占用大量时间的读取。

    我把这个分析器扔给它:http://code.google.com/p/slimtune/

    从那里我可以看到ParseLines 方法在内存流上调用时的时序如下:

    System.Io.StreamReader.ReadLine()25.34%
    23.86% 在System.Double.Parse
    21.72% 在System.Number.ParseInt32
    20.91% 在System.String.Split
    --其他一些不是很重要的方法--

    所以这告诉我的是,即使从内存流中读取行也有点慢,就像大多数字符串操作一样。

    【讨论】:

      【解决方案7】:

      我怀疑缓存会扭曲您所做的测量。硬盘有 RAM 用于缓存最近访问的数据,操作系统也有。

      为了获得更好的测试,您需要在测试之间重新启动。我建议完全断电以确保磁盘的 RAM 也被清除。

      【讨论】:

      • 忽略缓存,希望处理速度大于 20MB/s 仍然是合理的。
      • 重启后没有任何改变。我的 SSD 显然无法以尽可能快的速度传输数据。我已经与 Windows 的 ReadFile 进行了互操作,我可以在 0.6 秒内读取 200MB。所以IO绝对不是我的问题。中间有很多层。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-25
      • 1970-01-01
      • 2015-01-07
      相关资源
      最近更新 更多