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

时间:2012-12-29 07:05:54

标签: c# file-io visual-studio-2012 unmanaged managed

我有一个大量读取和写入文件的应用程序(自定义格式),我被告知通过使用直接非托管代码来提高性能。 在尝试使用真正的应用程序之前,我做了一些小测试,只是为了看看性能如何提升,但令我惊讶的是,非托管版本似乎比使用简单文件流慢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的人需要大约550毫秒。

我测试了几次并进行了几次迭代,性能上的差异是一致的。

我不知道为什么非托管代码比托管代码慢。

修改

非常感谢您的解释,伙计们。我认为有一些“魔法”正在经历FileStream,你已经解释得很好。 所以,我知道现在没有简单的途径可以获得这部分的性能,我想问你对其他简单方法获得速度的意见。该文件是实际应用程序中的随机访问,大小范围可以从1MB到1GB。

3 个答案:

答案 0 :(得分:3)

您的非托管调用会在FileStream缓冲时尽快将数据写入磁盘(即大多数操作在内存中并且应该更少地调用底层非托管调用)

如果您希望进一步调整性能,可以使用constructors on FileStream来控制缓冲区大小。

答案 1 :(得分:2)

嗯,FileStream是一个围绕CreateFile / WriteFile的包装器。它是由一群聪明人写的。所以我认为没有合理的解释为什么你认为你的那个应该更快:P。

如前所述,FileStream可能在调用WriteFile()之前进行额外缓冲,从而最大限度地减少了非托管方法调用。这很重要 - 只有在必要时才进行非托管呼叫。他们花了。缓冲区大小通常是磁盘扇区大小的倍数。您可以尝试不同的大小,但这取决于操作系统,并且很可能会在其他计算机上产生其他结果。

但是知道WriteFile()也进行内部缓冲也很重要。它不像你调用WriteFile()和bam它写入文件。一旦它的时间它将被刷新到HDD。

我认为有不必要的byte []封送正在进行中。例如,当您调用WriteFile()时,系统会复制您的缓冲区。应该通过unsafe()关键字和一点点黑客来避免它。

还有无法通过FileStream(afaik)访问的FILE_FLAG_SEQUENTIAL_SCAN,它应该让系统知道你将只按顺序执行文件写入/读取。这可能会在理论上提高性能。

答案 2 :(得分:1)

不同之处在于,对WriteFile的调用是同步的,而对FileStream的调用则不是。

默认情况下,CreateFile将创建一个同步文件句柄,因此在写入数据之前,对WriteFile的调用不会返回。如果您将FILE_FLAG_OVERLAPPED添加到CreateFile电话,则未管理的实施将与管理的实施时间大致相同。

请参阅CreateFile defini

Synchronous and Asynchronous I/O Handles部分的文档