0

我有一个对文件(自定义格式)进行大量读写的应用程序,有人告诉我通过使用直接非托管代码来提高性能。在尝试实际应用程序之前,我做了一个小测试,只是为了看看性能提升如何,但令我惊讶的是,非托管版本似乎比使用简单的文件流慢 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 不等。

4

3 回答 3

3

当 FileStream 被缓冲时,您的非托管调用会尽快将数据写入磁盘(即在内存中执行大多数操作,并且应该不那么频繁地调用底层非托管调用)

如果您想进一步调整性能,FileStream 上有一些构造函数可以让您控制缓冲区大小。

于 2012-12-29T07:13:20.060 回答
2

好吧,FileStream 只是 CreateFile/WriteFile 的一个包装器。这是一群聪明人写的。所以我完全看不出为什么你认为你的应该更快:P。

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

但同样重要的是要知道 WriteFile() 也进行内部缓冲。这不像您调用 WriteFile() 并将其写入文件。一旦时间到了,它将被刷新到HDD。

我认为正在进行不必要的 byte[] 编组。例如,当您调用 WriteFile() 时,系统会复制您的缓冲区。通过 unsafe() 关键字和一点点黑客攻击应该可以避免这种情况。

还有 FILE_FLAG_SEQUENTIAL_SCAN 无法通过 FileStream(afaik) 访问,它应该让系统知道您将仅按顺序进行文件写入/读取。从理论上讲,这可能会带来一些性能提升。

于 2012-12-29T07:49:08.520 回答
1

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

默认情况下CreateFile会创建一个同步文件句柄,所以在数据写入之前调用WriteFile不会返回。如果您添加FILE_FLAG_OVERLAPPEDCreateFile调用中,非托管实现将花费与托管大致相同的时间。

请参阅定义的同步和异步 I/O 句柄部分的CreateFile文档

于 2012-12-29T07:42:50.347 回答