Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/269.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么这个System.IO.Pipelines代码比基于流的代码慢得多?_C#_Performance_.net Core_System.io.pipelines - Fatal编程技术网

C# 为什么这个System.IO.Pipelines代码比基于流的代码慢得多?

C# 为什么这个System.IO.Pipelines代码比基于流的代码慢得多?,c#,performance,.net-core,system.io.pipelines,C#,Performance,.net Core,System.io.pipelines,我编写了一个小的解析程序来比较.NETCore中较旧的System.IO.Stream和较新的System.IO.Pipelines。我希望管道代码具有相同的速度或更快。但是,速度要慢40%左右 这个程序很简单:它在100Mb的文本文件中搜索关键字,并返回关键字的行号。以下是流版本: 公共静态异步任务GetLineNumberUsingStreamAsync( 字符串文件, 字符串(搜索词) { 使用var fileStream=File.OpenRead(文件); 使用var lines=ne

我编写了一个小的解析程序来比较.NETCore中较旧的
System.IO.Stream
和较新的
System.IO.Pipelines
。我希望管道代码具有相同的速度或更快。但是,速度要慢40%左右

这个程序很简单:它在100Mb的文本文件中搜索关键字,并返回关键字的行号。以下是流版本:

公共静态异步任务GetLineNumberUsingStreamAsync(
字符串文件,
字符串(搜索词)
{
使用var fileStream=File.OpenRead(文件);
使用var lines=newstreamreader(fileStream,bufferSize:4096);
int lineNumber=1;
//ReadLineAsync在流结束时返回null,退出循环
while(等待行。ReadLineAsync()是字符串行)
{
if(行包含(搜索词))
返回行号;
lineNumber++;
}
返回-1;
}
我希望上面的流代码比下面的管道代码慢,因为流代码在StreamReader中将字节编码为字符串。管道代码通过对字节进行操作来避免这种情况:

公共静态异步任务GetLineNumberSingPipeAsync(字符串文件,字符串搜索字)
{
var searchBytes=Encoding.UTF8.GetBytes(searchWord);
使用var fileStream=File.OpenRead(文件);
var pipe=PipeReader.Create(fileStream,newstreampipereaderoptions(bufferSize:4096));
变量lineNumber=1;
while(true)
{
var readResult=await pipe.ReadAsync().ConfigureAwait(false);
var buffer=readResult.buffer;
if(TryFindBytesInBuffer(ref buffer、searchBytes、ref lineNumber))
{
返回行号;
}
管道推进(缓冲器端);
如果(readResult.IsCompleted)中断;
}
wait pipe.CompleteAsync();
返回-1;
}
以下是相关的帮助器方法:

//
///在'buffer'中查找'searchBytes',每隔一天递增'lineNumber'
///我们该换一条新路线了。
/// 
///如果我们找到searchBytes,则为true,否则为false
静态布尔函数TryFindBytesInBuffer(
ref只读序列缓冲区,
在ReadOnlySpan searchBytes中,
参考整数(行号)
{
var bufferReader=新的SequenceReader(缓冲区);
while(TryReadLine(ref bufferReader,out var line))
{
if(包含字节(参考行,搜索字节))
返回true;
lineNumber++;
}
返回false;
}
静态布尔传输线(
参考SequenceReader bufferReader,
输出只读序列行)
{
var foundNewLine=bufferReader.TryReadTo(输出行,(字节)'\n',AdvanceCastDelimiter:true);
如果(!foundNewLine)
{
行=默认值;
返回false;
}
返回true;
}
静态布尔包含字节(
ref ref ReadOnlySequence行,
在ReadOnlySpan searchBytes中)
{
返回新的SequenceReader(line).TryReadTo(out var u,searchBytes);
}
我使用上面的
SequenceReader
,因为我的理解是它比
ReadOnlySequence
更智能/更快;当它可以在单个
Span
上操作时,它有一个快速路径

以下是基准测试结果(.NET Core 3.1)。完整的代码和BenchmarkDotNet结果可用

  • GetLineNumberWithStreamAsync-435.6毫秒,同时分配366.19 MB
  • GetLineNumberUsingPipeAsync-619.8 ms,同时分配9.28 MB
我在管道代码中是否做错了什么

更新:Evk已经回答了这个问题。应用他的修正后,以下是新的基准数字:

  • GetLineNumberWithStreamAsync-452.2毫秒,同时分配366.19 MB
  • GetLineNumberWithPipeAsync-203.8毫秒,分配9.28 MB

    • 这也许不是您想要的解释,但我希望它能提供一些见解:

      浏览一下这里的两种方法,第二种解决方案中的两个嵌套循环在计算上比另一种更复杂

      使用代码评测进行更深入的挖掘表明,第二个(GetLineNumberSingPipeAsync)比使用流的(请查看屏幕截图)高出近21.5%的CPU密集度,并且与我得到的基准测试结果非常接近:

      • 解决方案#1:683.7毫秒,365.84 MB

      • 溶液#2:777.5毫秒,9.08 MB


      我认为原因在于
      SequenceReader.TryReadTo的实现。这种方法的优点。它使用非常简单的算法(读取第一个字节的匹配,然后检查匹配后的所有后续字节,如果不匹配,则向前推进1个字节并重复),并注意在这个实现中有相当多的方法称为“慢”(
      IsNextSlow
      TryReadToSlow
      等等),因此,至少在某些情况下,在某些情况下,它会回到某种缓慢的道路上。它还必须处理序列可能包含多个段的事实,并保持位置

      在您的情况下,您可以避免专门使用
      SequenceReader
      来搜索匹配项(但将其留给实际读取行),例如,通过这种细微的更改(在这种情况下,
      TryReadTo
      的过载也更有效):

      private static bool TryReadLine(ref SequenceReader bufferReader,out ReadOnlySpan行){
      //请注意,“match”和“line”现在都是“ReadOnlySpan”,而不是“ReadOnlySequence”`
      var foundNewLine=bufferReader.TryReadTo(out ReadOnlySpan match,(byte)'\n',AdvanceCastDelimiter:true);
      如果(!foundNewLine){
      行=默认值;
      返回false;
      }
      直线=匹配;
      返回true;
      }
      
      然后:

      private static bool包含字节(ref ref ReadOnlySpan line,在ReadOnlySpan searchBytes中){
      //行现在是'ReadOnlySpan',所以我们可以使用有效的'IndexOf'方法
      
      private static bool TryReadLine(ref SequenceReader<byte> bufferReader, out ReadOnlySpan<byte> line) {
          // note that both `match` and `line` are now `ReadOnlySpan` and not `ReadOnlySequence`
          var foundNewLine = bufferReader.TryReadTo(out ReadOnlySpan<byte> match, (byte) '\n', advancePastDelimiter: true);
      
          if (!foundNewLine) {
              line = default;
              return false;
          }
      
          line = match;
          return true;
      }
      
      private static bool ContainsBytes(ref ReadOnlySpan<byte> line, in ReadOnlySpan<byte> searchBytes) {
          // line is now `ReadOnlySpan` so we can use efficient `IndexOf` method
          return line.IndexOf(searchBytes) >= 0;
      }