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

Mik*_*der 11 c# memorystream filestream

我有一个问题,我找不到理由.我正在创建一个自定义存档文件.我MemoryStream用来存储数据,最后我用a FileStream来将数据写入磁盘.

我的硬盘是SSD,但速度太慢了.当我尝试只向文件写入95 MB时,写入需要12秒!

我尝试了Filestream.Write,File.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);
Run Code Online (Sandbox Code Playgroud)

此外,file1.ToArray()只需90 ms即可将MemoryStream转换为字节.

为什么会发生这种情况,背后的原因和逻辑是什么?

Joh*_*nny 5

更新

Dmytro Mukalov是对的.通过扩展FileStream内部缓冲区获得的性能将在您实际执行时被删除Flush.我挖一个深一点,并做了一些基准,似乎那之间的区别Stream.CopyTo,并FileStream.WriteStream.CopyTo使用I/O缓冲聪明,由大块大块复制性能提升.最后在引擎盖下CopyTo使用Write.这里讨论了最佳缓冲区大小.

最佳缓冲区大小与许多因素有关:文件系统块大小,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
Run Code Online (Sandbox Code Playgroud)

原版的

我做的分析Write方法FileStreamMemoryStream并有该点MemoryStream总是使用内部缓冲区来复制数据,这是非常快的.在FileStream本身如果请求的开关count >= bufferSize,而你的情况是真实的,你正在使用默认的FileStream缓存,默认的缓冲区大小为4096.在那种情况下FileStream,除了本机之外根本不使用缓冲区Win32Native.WriteFile.

诀窍是FileStream通过覆盖默认缓冲区大小来强制使用缓冲区.试试这个:

// 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);
Run Code Online (Sandbox Code Playgroud)

我不是说它是最佳缓冲区大小只是解释发生了什么.要使用FileStream参考链接检查最佳缓冲区大小.