c# - 为什么 System/mscorlib 代码要快得多?特别是对于循环?

标签 c# performance file-io for-loop bytearray

这只是我一直在深入研究的个人项目。基本上,我使用 StreamReader 解析一个文本文件(比如从 2​​0mb 到大约 1gb)。性能非常稳定,但仍然......我一直很想知道如果我用二进制解析它会发生什么。别误会,我没有过早优化。我绝对是有意进行微优化,只是为了“看看”。

因此,我正在使用字节数组读取文本文件。来看看,新行可以是 (Windows) 标准 CR/LF 或 CR 或 LF……相当困惑。我曾希望能够在 CR 上使用 Array.IndexOf,然后跳过 LF。相反,我发现自己编写的代码与 IndexOf 非常相似,但会检查其中任何一个并根据需要返回一个数组。

所以关键是:使用与 IndexOf 非常相似的代码,我的代码最终仍然非常慢。使用 800mb 的文件进行透视:

  • 使用 IndexOf 并寻找 CR:~320mb/s
  • 使用 StreamReader 和 ReadLine:~180mb/s
  • for 循环复制 IndexOf:~150mb/s

这是带有 for 循环的代码 (~150mb/s):

IEnumerator<byte[]> IEnumerable<byte[]>.GetEnumerator() {
    using(FileStream fs = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, _bufferSize)) {
        byte[] buffer = new byte[_bufferSize];
        int bytesRead;
        int overflowCount = 0;
        while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
            int bufferLength = bytesRead + overflowCount;
            int lastPos = 0;
            for(int i = 0; i < bufferLength; i++) {
                if(buffer[i] == 13 || buffer[i] == 10) {
                    int length = i - lastPos;
                    if(length > 0) {
                        byte[] line = new byte[length];
                        Array.Copy(buffer, lastPos, line, 0, length);
                        yield return line;
                    }
                    lastPos = i + 1;
                }
            }
            if(lastPos > 0) {
                overflowCount = bufferLength - lastPos;
                Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
            }
        }
    }
}

这是更快的代码块(~320mb/s):

while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
    int bufferLength = bytesRead + overflowCount;
    int pos = 0;
    int lastPos = 0;
    while(pos < bufferLength && (pos = Array.IndexOf<byte>(buffer, 13, pos)) != -1) {
        int length = pos - lastPos;
        if(length > 0) {
            byte[] line = new byte[length];
            Array.Copy(buffer, lastPos, line, 0, length);
            yield return line;
        }
        if(pos < bufferLength - 1 && buffer[pos + 1] == 10)
            pos++;
        lastPos = ++pos;

    }
    if(lastPos > 0) {
        overflowCount = bufferLength - lastPos;
        Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
    }
}

(不,它还没有准备好生产,某些情况下它会崩溃;我使用 128kb 大小的缓冲区来忽略其中的大部分。)

所以我的大问题是……为什么 Array.IndexOf 的工作速度如此之快?它本质上是相同的,一个 for 循环遍历一个数组。 mscorlib 代码的执行方式有什么问题吗?即使将上面的代码更改为真正复制 IndexOf 并仅查找 CR,然后像我在使用 IndexOf 无济于事时那样跳过 LF。呃……我一直在经历各种排列,现在已经很晚了,也许我遗漏了一些明显的错误?

顺便说一句,我查看了 ReadLine 并注意到它使用了一个 switch block 而不是一个 if block ......当我做类似的事情时,奇怪的是它确实将性能提高了大约 15mb/s。这是另一次的另一个问题(为什么 switch 比 if 更快?)但我想我会指出我确实看过它。

此外,我正在 VS 之外测试发布版本,因此不会进行任何调试。

最佳答案

这是个好问题。简而言之,这一切都归结为 IndexOf 将使用的 IEqualityComparer 的实现。让我们看下面的一段代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Program {

    static int [] buffer = new int [1024];
    const byte mark = 42;
    const int iterations = 10000;

    static void Main ()
    {
        buffer [buffer.Length -1] = mark;

        Console.WriteLine (EqualityComparer<int>.Default.GetType ());

        Console.WriteLine ("Custom:  {0}", Time (CustomIndexOf));
        Console.WriteLine ("Builtin: {0}", Time (ArrayIndexOf));
    }

    static TimeSpan Time (Action action)
    {
        var watch = new Stopwatch ();
        watch.Start ();
        for (int i = 0; i < iterations; i++)
            action ();
        watch.Stop ();
        return watch.Elapsed;
    }

    static void CustomIndexOf ()
    {
        for (int i = 0; i < buffer.Length; i++)
            if (buffer [i] == mark)
                break;
    }

    static void ArrayIndexOf ()
    {
        Array.IndexOf (buffer, mark);
    }
}

您需要使用 csc/optimize+ 对其进行编译。

这是我得到的结果:

C:\Tmp>test
System.Collections.Generic.GenericEqualityComparer`1[System.Int32]
Custom:  00:00:00.0386403
Builtin: 00:00:00.0427903

现在,将数组和 EqualityComparer 的类型更改为 byte,这是我得到的结果:

C:\Tmp>test
System.Collections.Generic.ByteEqualityComparer
Custom:  00:00:00.0387158
Builtin: 00:00:00.0165881

如您所见,字节数组是特殊大小写的,它可能经过优化以在字节数组中查找字节。由于我无法反编译 .net 框架,所以我在这里停止了分析,但我想这是一个很好的线索。

关于c# - 为什么 System/mscorlib 代码要快得多?特别是对于循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/891835/

相关文章:

c# - 排序时ASP.net gridview数据源为空

c# - 在事件中使用 'this'

java - 使用带有套接字的 Javas 对象流的性能问题

c++ - 无论如何要将 filein 重置为初始状态?

c# - MoveFileWithProgress 抛出 "The system cannot move the file to a different disk drive"– 为什么?

c# - 从 ID 的 C# 列表中删除 mysql 服务器中的行

c# - 列表的 Clear() 使 Add() 更快?

java - 发送大量 POST 请求

python - 脚本加密,但仍然保存明文而不是密文

java - 测试用 Java 写入文件的方法