C# 为什么System/mscorlib代码要快得多?特别是循环?

C# 为什么System/mscorlib代码要快得多?特别是循环?,c#,performance,file-io,for-loop,bytearray,C#,Performance,File Io,For Loop,Bytearray,这只是我一直在钻研的一个个人项目。基本上,我使用StreamReader解析文本文件(比如从20mb到1gb)。表演相当稳定,但仍然。。。我一直渴望看到如果我用二进制解析它会发生什么。不要误解,我不是在过早地优化。我明确地说,微观优化的目的只是“看” 因此,我使用字节数组读取文本文件。来看看,新线可以是(Windows)标准CR/LF或CR或LF。。。很乱。我希望能够在CR上使用Array.IndexOf,然后跳过LF。相反,我发现自己编写的代码非常类似于IndexOf,但检查其中一个并根据需要

这只是我一直在钻研的一个个人项目。基本上,我使用StreamReader解析文本文件(比如从20mb到1gb)。表演相当稳定,但仍然。。。我一直渴望看到如果我用二进制解析它会发生什么。不要误解,我不是在过早地优化。我明确地说,微观优化的目的只是“看”

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

所以问题的关键是:使用与IndexOf非常相似的代码,我的代码仍然以惊人的速度结束。要使用800mb文件对其进行透视,请执行以下操作:

  • 使用IndexOf并查找CR:~320mb/s
  • 使用StreamReader和ReadLine:~180mb/s
  • 对于循环复制IndexOf:~150mb/s
下面是for循环(~150mb/s)的代码:

IEnumerator IEnumerable.GetEnumerator(){
使用(FileStream fs=newfilestream(_path,FileMode.Open,FileAccess.Read,FileShare.ReadWrite,_bufferSize)){
字节[]缓冲区=新字节[_bufferSize];
int字节读取;
int溢出计数=0;
而((bytesRead=fs.Read(buffer,overflowCount,buffer.Length-overflowCount))>0){
int bufferLength=字节读取+溢出计数;
int-lastPos=0;
for(int i=0;i0){
字节[]行=新字节[长度];
复制(缓冲区,最后位置,行,0,长度);
收益率回归线;
}
lastPos=i+1;
}
}
如果(lastPos>0){
overflowCount=缓冲区长度-lastPos;
复制(buffer,lastPos,buffer,0,overflowCount);
}
}
}
}
这是更快的代码块(~320mb/s):

while((bytesRead=fs.Read(buffer,overflowCount,buffer.Length-overflowCount))>0){
int bufferLength=字节读取+溢出计数;
int pos=0;
int-lastPos=0;
而(pos0){
字节[]行=新字节[长度];
复制(缓冲区,最后位置,行,0,长度);
收益率回归线;
}
if(pos0){
overflowCount=缓冲区长度-lastPos;
复制(buffer,lastPos,buffer,0,overflowCount);
}
}
(不,它还没有准备好生产,某些情况下会使它爆炸;我使用128kb大小的缓冲区来忽略大部分。)

所以我的大问题是。。。为什么Array.IndexOf工作得这么快?它本质上是相同的,用于遍历数组的循环。mscorlib代码的执行方式有什么问题吗?即使将上面的代码更改为真正复制IndexOf,只查找CR,然后像使用IndexOf没有帮助时那样跳过LF。呃。。。我已经经历了各种各样的排列,现在已经足够晚了,也许我遗漏了一些明显的bug

顺便说一句,我查看了ReadLine,发现它使用的是开关块而不是if块。。。当我做类似的事情时,奇怪的是,它确实将性能提高了约15mb/s。这是下一次的另一个问题(为什么切换速度比if快?),但我想我应该指出,我确实看过它


另外,我正在VS之外测试一个发布版本,因此没有调试工作进行。

在安装过程中,mscorlib文件会被加密。 尝试使用ngen.exe实用程序(我想是与.NET framwork一起提供的)对文件进行加密。。。然后检查基准。 可以稍微快一点


为了使.NET代码以接近本机速度运行,Microsoft建议您在安装应用程序时“加密”代码…

这是个好问题。简短的版本是,这一切归结为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);
    }
}
现在,将数组和EqualityComparer的类型更改为byte,结果如下:

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

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

Hmm,很有趣。使用Reflector查看mscorlib变体,我没有看到它可能使用的任何诡计。它确实最终使用了诡计。默认结果是使用ByteEqualityComparer。我一开始也没有注意到,因为IndexOf有一个默认实现。检查默认属性,它会执行一个shell游戏,并在byte.ngen的特殊情况下进行交换。ngen实际上只执行即时编译器所执行的操作。不同之处在于它提前完成了任务。(所以当第二次运行代码时,同样的结果也会出现……因为它已经被预编译了。)宾果!它最终使用ByteEqualityComparer,而ByteEqualityComparer又使用指针来使用缓冲区。做类似的事情,我现在达到了~350mb/s。必须说,我对管理层的表现相当失望。我不确定我是否真的想为这个小项目陷入不安全的境地。
C:\Tmp>test
System.Collections.Generic.GenericEqualityComparer`1[System.Int32]
Custom:  00:00:00.0386403
Builtin: 00:00:00.0427903
C:\Tmp>test
System.Collections.Generic.ByteEqualityComparer
Custom:  00:00:00.0387158
Builtin: 00:00:00.0165881