2012-10-15 25 views
7

Một số mã tôi đang làm việc với đôi khi cần tham chiếu đến đường dẫn UNC dài (ví dụ: \\? \ UNC \ MachineName \ Path), nhưng chúng tôi đã phát hiện ra nơi thư mục được đặt, ngay cả trên cùng một máy, nó chậm hơn nhiều khi truy cập thông qua đường dẫn UNC hơn đường dẫn cục bộ.Đường dẫn UNC trỏ đến thư mục cục bộ chậm hơn nhiều so với truy cập cục bộ

Ví dụ: chúng tôi đã viết một số mã điểm chuẩn viết một chuỗi vô nghĩa cho một tệp, sau đó đọc lại, nhiều lần. Tôi đang thử nghiệm nó với 6 cách khác nhau để truy cập vào thư mục chia sẻ tương tự trên máy dev của tôi, với mã chạy trên cùng một máy:

  • C: \ Temp
  • \\ MachineName \ Temp
  • ? \\ \ C:? \ Temp
  • \\ \ UNC \ MachineName \ Temp
  • \\ 127.0.0.1 \ Temp
  • \\ \ UNC \ 127.0.0.1 \ Temp

Và đây là kết quả:

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 

Tôi đã thử địa chỉ IP để loại trừ vấn đề DNS. Có thể kiểm tra thông tin đăng nhập hoặc quyền đối với mỗi quyền truy cập tệp không? Nếu vậy, có cách nào để lưu nó không? Liệu nó chỉ giả định vì nó là một đường dẫn UNC mà nó nên làm mọi thứ trên TCP/IP thay vì truy cập trực tiếp vào đĩa? Có điều gì sai với mã chúng tôi đang sử dụng cho lần đọc/ghi không? Tôi đã tách các phần thích hợp cho điểm chuẩn, xem bên dưới:

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); 
       } 
      } 
     } 
    } 
} 

Trả lời

6

Điều này không làm tôi ngạc nhiên. Bạn đang viết/đọc một lượng dữ liệu khá nhỏ, do đó, bộ nhớ cache của hệ thống tệp có thể giảm thiểu tác động của I/O đĩa vật lý; về cơ bản, nút cổ chai sẽ là CPU. Tôi không chắc chắn liệu lưu lượng truy cập sẽ đi qua ngăn xếp TCP/IP hay không nhưng ở mức tối thiểu giao thức SMB có liên quan. Đối với một điều có nghĩa là các yêu cầu đang được chuyển qua lại giữa quy trình máy khách SMB và quy trình máy chủ SMB, vì vậy bạn đã chuyển ngữ cảnh giữa ba quy trình riêng biệt, bao gồm cả các quy trình của riêng bạn. Sử dụng đường dẫn hệ thống tệp cục bộ bạn đang chuyển sang chế độ hạt nhân và ngược lại nhưng không có quá trình nào khác có liên quan. Chuyển đổi ngữ cảnh là nhiều hơn chậm hơn chuyển tiếp sang và từ chế độ hạt nhân.

Có thể có hai chi phí bổ sung riêng biệt, một trên mỗi tệp và một trên mỗi kilobyte dữ liệu. Trong thử nghiệm đặc biệt này, mỗi tập tin SMB trên cao có thể sẽ chiếm ưu thế. Bởi vì số lượng dữ liệu liên quan cũng ảnh hưởng đến tác động của đĩa vật lý I/O, bạn có thể thấy rằng đây chỉ thực sự là một vấn đề khi giao dịch với rất nhiều tập tin nhỏ.

Các vấn đề liên quan