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

问题描述 投票:11回答:1

我有一个问题,我找不到理由。我正在创建一个自定义存档文件。我正在使用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 ms即可将MemoryStream转换为字节。

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

c# filestream memorystream
1个回答
5
投票

Update

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

Original

我对WriteFileStreamMemoryStream方法进行了分析,并指出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

© www.soinside.com 2019 - 2024. All rights reserved.