我有一个问题,我找不到理由。我正在创建一个自定义存档文件。我正在使用MemoryStream
存储数据,最后我使用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);
此外,file1.ToArray()
只需90 ms即可将MemoryStream转换为字节。
为什么会发生这种情况,背后的原因和逻辑是什么?
Dmytro Mukalov有权利。当你做真正的FileStream
时,通过扩展Flush
内部缓冲区获得的性能将被带走。我深入挖掘并做了一些基准测试,看起来Stream.CopyTo
和FileStream.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
我对Write
和FileStream
的MemoryStream
方法进行了分析,并指出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);
注:我不是说它是最佳缓冲区大小只是解释发生了什么。要使用FileStream
检查最佳缓冲区大小,请参阅link。