c# - 指向本地目录的 UNC 路径比本地访问慢得多

标签 c# windows file-io unc

我使用的一些代码偶尔需要引用较长的 UNC 路径(例如\\?\UNC\MachineName\Path),但我们发现无论目录位于何处,即使在同一台机器上也是如此,通过UNC路径访问比本地路径慢很多。

例如,我们编写了一些基准测试代码,将一串乱码写入文件,然后多次读回。我正在用 6 种不同的方式测试它以访问我的开发机器上的相同共享目录,代码在同一台机器上运行:

  • C:\Temp
  • \\机器名\Temp
  • \\?\C:\Temp
  • \\?\UNC\机器名\Temp
  • \\127.0.0.1\Temp
  • \\?\UNC\127.0.0.1\Temp

结果如下:

Testing: C:\Temp
Wrote 1000 files to C:\Temp in 861.0647 ms
Read 1000 files from C:\Temp in 60.0744 ms
Testing: \\MachineName\Temp
Wrote 1000 files to \\MachineName\Temp in 2270.2051 ms
Read 1000 files from \\MachineName\Temp in 1655.0815 ms
Testing: \\?\C:\Temp
Wrote 1000 files to \\?\C:\Temp in 916.0596 ms
Read 1000 files from \\?\C:\Temp in 60.0517 ms
Testing: \\?\UNC\MachineName\Temp
Wrote 1000 files to \\?\UNC\MachineName\Temp in 2499.3235 ms
Read 1000 files from \\?\UNC\MachineName\Temp in 1684.2291 ms
Testing: \\127.0.0.1\Temp
Wrote 1000 files to \\127.0.0.1\Temp in 2516.2847 ms
Read 1000 files from \\127.0.0.1\Temp in 1721.1925 ms
Testing: \\?\UNC\127.0.0.1\Temp
Wrote 1000 files to \\?\UNC\127.0.0.1\Temp in 2499.3211 ms
Read 1000 files from \\?\UNC\127.0.0.1\Temp in 1678.18 ms

我尝试了 IP 地址以排除 DNS 问题。它会检查每个文件访问的凭据或权限吗?如果是这样,有没有办法缓存它?由于它是 UNC 路径,它是否只是假设它应该通过 TCP/IP 执行所有操作而不是直接访问磁盘?我们用于读/写的代码有问题吗?我已经删除了用于基准测试的相关部分,如下所示:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
using Util.FileSystem;

namespace UNCWriteTest {
    internal class Program {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool DeleteFile(string path); // File.Delete doesn't handle \\?\UNC\ paths

        private const int N = 1000;

        private const string TextToSerialize =
            "asd;lgviajsmfopajwf0923p84jtmpq93worjgfq0394jktp9orgjawefuogahejngfmliqwegfnailsjdhfmasodfhnasjldgifvsdkuhjsmdofasldhjfasolfgiasngouahfmp9284jfqp92384fhjwp90c8jkp04jk34pofj4eo9aWIUEgjaoswdfg8jmp409c8jmwoeifulhnjq34lotgfhnq34g";

        private static readonly byte[] _Buffer = Encoding.UTF8.GetBytes(TextToSerialize);

        public static string WriteFile(string basedir) {
            string fileName = Path.Combine(basedir, string.Format("{0}.tmp", Guid.NewGuid()));

            try {
                IntPtr writeHandle = NativeFileHandler.CreateFile(
                    fileName,
                    NativeFileHandler.EFileAccess.GenericWrite,
                    NativeFileHandler.EFileShare.None,
                    IntPtr.Zero,
                    NativeFileHandler.ECreationDisposition.New,
                    NativeFileHandler.EFileAttributes.Normal,
                    IntPtr.Zero);

                // if file was locked
                int fileError = Marshal.GetLastWin32Error();
                if ((fileError == 32 /* ERROR_SHARING_VIOLATION */) || (fileError == 80 /* ERROR_FILE_EXISTS */)) {
                    throw new Exception("oopsy");
                }

                using (var h = new SafeFileHandle(writeHandle, true)) {
                    using (var fs = new FileStream(h, FileAccess.Write, NativeFileHandler.DiskPageSize)) {
                        fs.Write(_Buffer, 0, _Buffer.Length);
                    }
                }
            }
            catch (IOException) {
                throw;
            }
            catch (Exception ex) {
                throw new InvalidOperationException(" code " + Marshal.GetLastWin32Error(), ex);
            }

            return fileName;
        }

        public static void ReadFile(string fileName) {
            var fileHandle =
                new SafeFileHandle(
                    NativeFileHandler.CreateFile(fileName, NativeFileHandler.EFileAccess.GenericRead, NativeFileHandler.EFileShare.Read, IntPtr.Zero,
                                                 NativeFileHandler.ECreationDisposition.OpenExisting, NativeFileHandler.EFileAttributes.Normal, IntPtr.Zero), true);

            using (fileHandle) {
                //check the handle here to get a bit cleaner exception semantics
                if (fileHandle.IsInvalid) {
                    //ms-help://MS.MSSDK.1033/MS.WinSDK.1033/debug/base/system_error_codes__0-499_.htm
                    int errorCode = Marshal.GetLastWin32Error();
                    //now that we've taken more than our allotted share of time, throw the exception
                    throw new IOException(string.Format("file read failed on {0} to {1} with error code {1}", fileName, errorCode));
                }

                //we have a valid handle and can actually read a stream, exceptions from serialization bubble out
                using (var fs = new FileStream(fileHandle, FileAccess.Read, 1*NativeFileHandler.DiskPageSize)) {
                    //if serialization fails, we'll just let the normal serialization exception flow out
                    var foo = new byte[256];
                    fs.Read(foo, 0, 256);
                }
            }
        }

        public static string[] TestWrites(string baseDir) {
            try {
                var fileNames = new List<string>();
                DateTime start = DateTime.UtcNow;
                for (int i = 0; i < N; i++) {
                    fileNames.Add(WriteFile(baseDir));
                }
                DateTime end = DateTime.UtcNow;

                Console.Out.WriteLine("Wrote {0} files to {1} in {2} ms", N, baseDir, end.Subtract(start).TotalMilliseconds);
                return fileNames.ToArray();
            }
            catch (Exception e) {
                Console.Out.WriteLine("Failed to write for " + baseDir + " Exception: " + e.Message);
                return new string[] {};
            }
        }

        public static void TestReads(string baseDir, string[] fileNames) {
            try {
                DateTime start = DateTime.UtcNow;

                for (int i = 0; i < N; i++) {
                    ReadFile(fileNames[i%fileNames.Length]);
                }
                DateTime end = DateTime.UtcNow;

                Console.Out.WriteLine("Read {0} files from {1} in {2} ms", N, baseDir, end.Subtract(start).TotalMilliseconds);
            }
            catch (Exception e) {
                Console.Out.WriteLine("Failed to read for " + baseDir + " Exception: " + e.Message);
            }
        }

        private static void Main(string[] args) {
            foreach (string baseDir in args) {
                Console.Out.WriteLine("Testing: {0}", baseDir);

                string[] fileNames = TestWrites(baseDir);

                TestReads(baseDir, fileNames);

                foreach (string fileName in fileNames) {
                    DeleteFile(fileName);
                }
            }
        }
    }
}

最佳答案

这并不让我感到惊讶。您正在写入/读取相当少量的数据,因此文件系统缓存可能会最大限度地减少物理磁盘 I/O 的影响;基本上,瓶颈将是 CPU。我不确定流量是否会通过 TCP/IP 堆栈,但至少涉及 SMB 协议(protocol)。一方面,这意味着请求在 SMB 客户端进程和 SMB 服务器进程之间来回传递,因此您可以在三个不同的进程(包括您自己的进程)之间进行上下文切换。使用本地文件系统路径,您将切换到内核模式并返回,但不涉及其他进程。上下文切换比进出内核模式的转换要慢很多

可能有两种不同的额外开销,一种是每个文件,另一种是每千字节数据。在此特定测试中,每个文件的 SMB 开销可能占主导地位。因为涉及的数据量也会影响物理磁盘 I/O 的影响,您可能会发现这只是在处理大量小文件时才真正成为问题。

关于c# - 指向本地目录的 UNC 路径比本地访问慢得多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12903054/

相关文章:

c# - SignInAsAuthenticationType 是否允许我在不覆盖现有声明的情况下获取 OAuth token ?

c# - 使用文本框在另一页上编辑 ListView 行

javascript - ESlint 初始化设置未显示 VSCode Windows 操作系统

c - 使用主应用程序窗口使拥有的窗口最小化

c# - 如何快速创建内容为 "natural"的大型 (>1gb) 文本+二进制文件? (C#)

c# - 如何在 C# 中更改文件创建时间?

c# - 将 Markitup 文本编辑器集成到 ASP.NET MVC 项目中

c# - 为什么我的 Azure Functions 显示 "Cannot bind parameter ' $return' to type IActionResult”消息?

java - 删除直到昨天在目录(但不是目录)中创建的所有文件 - 一种线性解决方案

Java 随机文件访问 : Get byte offset of line start