非托管文件访问(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。

非托管文件访问(WriteFile)比托管文件访问(FileStream)慢

当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句柄部分的文档