非托管文件访问(WriteFile)比托管文件访问(FileStream)慢
本文关键字:访问 文件 FileStream WriteFile | 更新日期: 2023-09-27 17:50:00
我有一个大量读写文件(自定义格式)的应用程序,有人告诉我通过使用直接非托管代码来提高性能。在尝试在实际应用程序中使用之前,我做了一个小测试,只是为了看看性能增益如何,但令我惊讶的是,非托管版本似乎比使用简单的fileststream慢8倍。
下面是托管函数:
private int length = 100000;
private TimeSpan tspan;
private void UsingManagedFileHandle()
{
DateTime initialTime = DateTime.Now;
using (FileStream fileStream = new FileStream("data2.txt", FileMode.Create, FileAccess.ReadWrite))
{
string line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123";
byte[] bytes = Encoding.Unicode.GetBytes(line);
for (int i = 0; i < length; i++)
{
fileStream.Write(bytes, 0, bytes.Length);
}
fileStream.Close();
}
this.tspan = DateTime.Now.Subtract(initialTime);
label2.Text = "" + this.tspan.TotalMilliseconds + " Milliseconds";
}
非托管方式:
public void UsingAnUnmanagedFileHandle()
{
DateTime initialTime;
IntPtr hFile;
hFile = IntPtr.Zero;
hFile = FileInteropFunctions.CreateFile("data1.txt",
FileInteropFunctions.GENERIC_WRITE | FileInteropFunctions.GENERIC_READ,
FileInteropFunctions.FILE_SHARE_WRITE,
IntPtr.Zero,
FileInteropFunctions.CREATE_ALWAYS,
FileInteropFunctions.FILE_ATTRIBUTE_NORMAL,
0);
uint lpNumberOfBytesWritten = 0;
initialTime = DateTime.Now;
if (hFile.ToInt64() > 0)
{
string line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123";
byte[] bytes = Encoding.Unicode.GetBytes(line);
uint bytesLen = (uint)bytes.Length;
for (int i = 0; i < length; i++)
{
FileInteropFunctions.WriteFile(hFile,
bytes,
bytesLen,
out lpNumberOfBytesWritten,
IntPtr.Zero);
}
FileInteropFunctions.CloseHandle(hFile);
this.tspan = DateTime.Now.Subtract(initialTime);
label1.Text = "" + this.tspan.TotalMilliseconds + " Milliseconds";
}
else
label1.Text = "Error";
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern unsafe IntPtr CreateFile(
String lpFileName, // Filename
uint dwDesiredAccess, // Access mode
uint dwShareMode, // Share mode
IntPtr attr, // Security Descriptor
uint dwCreationDisposition, // How to create
uint dwFlagsAndAttributes, // File attributes
uint hTemplateFile); // Handle to template file
[DllImport("kernel32.dll")]
public static extern unsafe int WriteFile(IntPtr hFile,
// byte[] lpBuffer,
[MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer, // also tried this.
uint nNumberOfBytesToWrite,
out uint lpNumberOfBytesWritten,
IntPtr lpOverlapped);
使用FileStream的迭代在我的计算机中大约需要70毫秒。使用WriteFile的程序耗时约550ms。
我测试了几次,进行了几次迭代,性能上的差异是一致的。
我不知道为什么非托管代码比托管代码慢。
编辑
谢谢你的解释,伙计们。我以为FileStream中有什么"神奇"的东西,你解释得很好。所以,我知道现在在这部分没有简单的方法来获得性能,我想问你关于其他简单的方法来获得速度的意见。该文件在实际应用程序中是随机访问的,大小范围从1MB到1GB。
当FileStream被缓冲时,您的非托管调用会尽快将数据写入磁盘(即在内存中执行大多数操作,并且应该更少地调用底层非托管调用)
如果你想进一步调整性能,FileStream上有一些构造函数可以让你控制缓冲区大小。
嗯,FileStream只是CreateFile/WriteFile的包装。它是由一群聪明人写的。所以我完全看不出你为什么认为你的应该更快:p
如前所述,FileStream可能在调用WriteFile()之前进行额外缓冲,从而最大限度地减少非托管方法调用。这一点很重要——只在必要时拨打非托管电话。他们的成本。缓冲区大小通常是磁盘扇区大小的数倍。您可以尝试不同的大小,尽管这取决于操作系统,并且很可能在其他计算机上产生其他结果。
但是知道WriteFile()也做内部缓冲也是很重要的。这不像你调用WriteFile()然后它就写入文件了。它将被刷新到硬盘一旦它的时间。
我认为存在不必要的byte[]封送处理。例如,当你调用WriteFile()时,系统会复制你的缓冲区。它应该可以通过不安全()关键字和一点点黑客攻击来避免。
也有FILE_FLAG_SEQUENTIAL_SCAN不能通过FileStream(错误)访问,它应该让系统知道你要做文件写/读顺序。
区别在于对WriteFile
的调用是同步的,而对FileStream
的写入不是同步的。
默认情况下,CreateFile
将创建一个同步文件句柄,因此对WriteFile
的调用在数据写入之前不会返回。如果将FILE_FLAG_OVERLAPPED
添加到CreateFile
调用中,则非托管实现将花费与托管实现大致相同的时间。
请参阅CreateFile
定义的同步和异步I/O句柄部分的文档