2012-10-15 11 views
7

Alcuni codici con cui lavoro occasionalmente devono fare riferimento a percorsi UNC lunghi (ad esempio \\? \ UNC \ MachineName \ Path), ma abbiamo scoperto che non importa dove si trova la directory, anche sulla stessa macchina, è molto più lento quando si accede attraverso il percorso UNC rispetto al percorso locale.Percorso UNC che punta alla directory locale molto più lentamente dell'accesso locale

Ad esempio, abbiamo scritto un codice di benchmarking che scrive una stringa di parole senza senso in un file, quindi la rilegge in seguito, più volte. Sto testando con 6 diversi modi per accedere alla stessa directory condivisa sulla mia macchina dev, con il codice in esecuzione sulla stessa macchina:

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

Ed ecco i risultati:

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 

Ho provato l'indirizzo IP per escludere un problema DNS. Potrebbe controllare credenziali o permessi su ciascun accesso ai file? Se è così, c'è un modo per nasconderlo? Assume solo perché è un percorso UNC che dovrebbe fare tutto su TCP/IP invece di accedere direttamente al disco? C'è qualcosa di sbagliato nel codice che stiamo usando per le letture/scritture? Ho strappato le parti pertinenti per il benchmarking, vedi sotto:

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

risposta

6

Questo non mi sorprende. Stai scrivendo/leggendo una piccola quantità di dati, quindi la cache del file system probabilmente sta riducendo al minimo l'impatto dell'I/O del disco fisico; in sostanza, il collo di bottiglia sarà la CPU. Non sono sicuro se il traffico verrà gestito tramite lo stack TCP/IP o meno, ma almeno il protocollo SMB è coinvolto. Per prima cosa, significa che le richieste vengono passate avanti e indietro tra il processo del client SMB e il processo del server SMB, quindi hai il cambio di contesto tra tre processi distinti, incluso il tuo. Usando il percorso del file system locale stai passando alla modalità kernel e ritorno ma non è coinvolto nessun altro processo. Il cambio di contesto è molto più lento di rispetto alla transizione da e verso la modalità kernel.

È probabile che ci siano due distinti overhead aggiuntivi, uno per file e uno per kilobyte di dati. In questo particolare test è probabile che il sovraccarico per file SMB sia dominante. Poiché la quantità di dati coinvolti influisce anche sull'impatto di I/O su disco fisico, è possibile che si tratti solo di un problema quando si gestiscono molti piccoli file.

Problemi correlati