为什么Stream.Copy比Stream.Write更快到FileStream?

时间:2019-02-11 07:03:45

标签: c# filestream memorystream

我有一个问题,我找不到原因。 我正在创建一个自定义存档文件。我使用MemoryStream存储数据,最后使用FileStream将数据写入磁盘。

我的硬盘是 SSD ,但是速度太慢。当我尝试仅将95 MB的内容写入文件时,花了12秒钟才能写入!

我尝试过Filestream.WriteFile.WriteAllBytes,但相同。

最后我有了一个复制的想法,它快了 100倍!

我需要知道为什么会这样以及写函数出了什么问题。

这是我的代码:

//// First of all I create an example 150MB file
Random randomgen = new Random();
byte[] new_byte_array = new byte[150000000];
randomgen.NextBytes(new_byte_array);

//// I turned the byte array into a MemoryStream
MemoryStream file1 = new MemoryStream(new_byte_array);
//// HERE I DO SOME THINGS WITH THE MEMORYSTREAM


/// Method 1 : File.WriteAllBytes | 13,944 ms
byte[] output = file1.ToArray();
File.WriteAllBytes("output.test", output);

// Method 2 : FileStream | 8,471 ms
byte[] output = file1.ToArray();
FileStream outfile = new FileStream("outputfile",FileMode.Create,FileAccess.ReadWrite);
outfile.Write(output,0, output.Length);

// Method 3 | FileStream | 147 ms !!!! :|
FileStream outfile = new FileStream("outputfile",FileMode.Create,FileAccess.ReadWrite);
file1.CopyTo(outfile);

此外,file1.ToArray()仅需要90毫秒即可将MemoryStream转换为字节。

为什么会这样,其背后的原因和逻辑是什么?

1 个答案:

答案 0 :(得分:5)

更新

Dmytro Mukalov有权利。当您实际进行FileStream时,通过扩展Flush内部缓冲区获得的性能将被取消。我进行了更深入的研究,并进行了一些基准测试,看来Stream.CopyToFileStream.Write之间的区别在于Stream.CopyTo更智能地使用I / O缓冲区并通过逐块复制来提高性能。最后,CopyTo在引擎盖下使用Write。最佳缓冲区大小已在here中进行了讨论。

  

最佳缓冲区大小与许多因素有关:文件系统   块大小,CPU缓存大小和缓存延迟。大多数文件系统是   配置为使用4096或8192的块大小。理论上,如果您   配置缓冲区大小,以便读取的字节数比   磁盘块,文件系统的操作可能非常繁琐   效率低下(即如果您将缓冲区配置为在以下位置读取4100字节   一次,每次读取将需要文件系统进行2次块读取)。如果   块已经在缓存中,然后您最终付出代价   RAM-> L3 / L2缓存延迟。如果您不走运,但障碍却不是   在缓存中,您还需要付出磁盘-> RAM延迟的代价。

所以要回答您的问题,就您而言,使用Write时使用的是未优化的缓冲区大小,而使用CopyTo时则进行了优化,或者更好的说Stream本身会针对你。

通常,您可以通过扩展CopyTo内部缓冲区来强制未优化的FileStream,在这种情况下,结果应比未优化的Write慢。

FileStream outfile = new FileStream("outputfile",
    FileMode.Create, 
    FileAccess.ReadWrite,
    FileShare.Read,
    150000000); //internal buffer will lead to inefficient disk write
file1.CopyTo(outfile);
outfile.Flush(); //don't forget to flush data to disk

原始

我对WriteFileStream的{​​{1}}方法进行了分析,结果是MemoryStream始终使用内部缓冲区来复制数据,并且非常快。 MemoryStream本身有一个开关,如果请求的FileStream(在您使用默认count >= bufferSize缓冲区的情况下是正确的)上,则默认情况是FileStream。在这种情况下,4096除了本地FileStream根本不使用缓冲区。

技巧是通过覆盖默认缓冲区大小来强制Win32Native.WriteFile使用缓冲区。试试这个:

FileStream

n.b。我并不是说这是最佳的缓冲区大小,只是为了解释发生了什么。要使用// Method 2 : FileStream | 8,471 ms byte[] output = file1.ToArray(); FileStream outfile = new FileStream("outputfile", FileMode.Create, FileAccess.ReadWrite, FileShare.Read, output.Length + 1); // important, the size of the buffer outfile.Write(output, 0, output.Length); 检查最佳缓冲区大小,请参考link