2013-08-20 22 views
26

Stiamo provando a misurare le prestazioni tra la lettura di una serie di file utilizzando i metodi di sincronizzazione vs async. Mi aspettavo di avere circa lo stesso tempo tra i due ma risulta che usando async è circa 5,5 volte più lento.C# 4.5 file read sync performance async

Questo potrebbe essere dovuto al sovraccarico di gestione dei thread ma volevo solo sapere la tua opinione. Forse stiamo solo misurando i tempi sbagliati.

Questi sono i metodi in fase di sperimentazione:

static void ReadAllFile(string filename) 
    { 
     var content = File.ReadAllBytes(filename); 
    } 

    static async Task ReadAllFileAsync(string filename) 
    { 
     using (var file = File.OpenRead(filename)) 
     { 
      using (var ms = new MemoryStream()) 
      { 
       byte[] buff = new byte[file.Length]; 
       await file.ReadAsync(buff, 0, (int)file.Length); 
      } 
     } 
    } 

e questo è il metodo che li gestisce e avvia il cronometro:

static void Test(string name, Func<string, Task> gettask, int count) 
    { 
     Stopwatch sw = new Stopwatch(); 

     Task[] tasks = new Task[count]; 
     sw.Start(); 
     for (int i = 0; i < count; i++) 
     { 
      string filename = "file" + i + ".bin"; 
      tasks[i] = gettask(filename); 
     } 
     Task.WaitAll(tasks); 
     sw.Stop(); 
     Console.WriteLine(name + " {0} ms", sw.ElapsedMilliseconds); 

    } 

che è tutto eseguito da qui:

static void Main(string[] args) 
    { 
     int count = 10000; 

     for (int i = 0; i < count; i++) 
     { 
      Write("file" + i + ".bin"); 
     } 

     Console.WriteLine("Testing read...!");    

     Test("Read Contents", (filename) => Task.Run(() => ReadAllFile(filename)), count); 
     Test("Read Contents Async", (filename) => ReadAllFileAsync(filename), count); 

     Console.ReadKey(); 
    } 

E il metodo di scrittura dell'helper:

static void Write(string filename) 
    { 
     Data obj = new Data() 
     { 
      Header = "random string size here" 
     }; 
     int size = 1024 * 20; // 1024 * 256; 

     obj.Body = new byte[size]; 

     for (var i = 0; i < size; i++) 
     { 
      obj.Body[i] = (byte)(i % 256); 
     } 

     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 

     MemoryStream ms = new MemoryStream(); 
     Serializer.Serialize(ms, obj); 
     ms.Position = 0; 

     using (var file = File.Create(filename)) 
     { 
      ms.CopyToAsync(file).Wait(); 
     } 

     sw.Stop(); 
     //Console.WriteLine("Writing file {0}", sw.ElapsedMilliseconds); 
    } 

I risultati:

-Read Contents 574 ms 
-Read Contents Async 3160 ms 

sarà davvero apprezzare se qualcuno può far luce su questo come abbiamo cercato la pila e il web, ma non può davvero trovare una spiegazione adeguata.

+0

Il test potrebbe essere difettoso mentre si generano thread per eseguire contemporaneamente le letture. Un test migliore sarebbe testare su cosa e poi testare l'altro. – Kami

+1

Su una nota diversa, c'è un metodo statico elegante su Stopwatch chiamato 'StartNew', fondamentalmente fa' var s = new Stopwatch(); s.Start(); return s; 'quindi non devi. :-) – Patrick

+0

Penso che quel test sia difettoso. Hai misurato la differenza tra ReadAllBytes e Read?Questa potrebbe essere la prima cosa, che ReadAllBytes è più efficiente - forse è un'operazione "atomica"? – TGlatzer

risposta

42

Ci sono un sacco di cose sbagliate con il codice di prova. In particolare, il test "asincrono" non utilizza l'I/O asincrono; con i flussi di file, è necessario aprirli esplicitamente come asincroni oppure eseguire operazioni sincrone su un thread in background. Inoltre, le dimensioni dei file sono molto piccole e possono essere facilmente memorizzate nella cache.

ho modificato il codice di prova di scrivere i file molto più grandi, di avere sincronizzazione paragonabile vs codice asincrono, e per rendere il codice asincrono asincrono:

static void Main(string[] args) 
{ 
    Write("0.bin"); 
    Write("1.bin"); 
    Write("2.bin"); 

    ReadAllFile("2.bin"); // warmup 

    var sw = new Stopwatch(); 
    sw.Start(); 
    ReadAllFile("0.bin"); 
    ReadAllFile("1.bin"); 
    ReadAllFile("2.bin"); 
    sw.Stop(); 

    Console.WriteLine("Sync: " + sw.Elapsed); 

    ReadAllFileAsync("2.bin").Wait(); // warmup 

    sw.Restart(); 
    ReadAllFileAsync("0.bin").Wait(); 
    ReadAllFileAsync("1.bin").Wait(); 
    ReadAllFileAsync("2.bin").Wait(); 
    sw.Stop(); 

    Console.WriteLine("Async: " + sw.Elapsed); 

    Console.ReadKey(); 
} 

static void ReadAllFile(string filename) 
{ 
    using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false)) 
    { 
     byte[] buff = new byte[file.Length]; 
     file.Read(buff, 0, (int)file.Length); 
    } 
} 

static async Task ReadAllFileAsync(string filename) 
{ 
    using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) 
    { 
     byte[] buff = new byte[file.Length]; 
     await file.ReadAsync(buff, 0, (int)file.Length); 
    } 
} 

static void Write(string filename) 
{ 
    int size = 1024 * 1024 * 256; 
    var data = new byte[size]; 
    var random = new Random(); 
    random.NextBytes(data); 
    File.WriteAllBytes(filename, data); 
} 

Sulla mia macchina, questo test (costruito in uscita, Esegui al di fuori del debugger) restituisce questi numeri:

Sync: 00:00:00.4461936 
Async: 00:00:00.4429566 
+1

Grazie per aver segnalato questo. Ho ottenuto circa gli stessi risultati sulla mia macchina che ora ha molto più senso. Questo è in realtà un test per una cache di file e stiamo prototipando il modo migliore per leggere un sacco di piccoli file. – gcastelo

+0

Ciao Stephen, se la dimensione del file è grande, diciamo diverse centinaia di Mbs. Questo codice sarebbe rotto ?? byte [] buff = new byte [file.Length]; attende file.ReadAsync (buff, 0, (int) file.Length); Perché cerca di allocare una grossa fetta di memoria? –

+0

@ToanNguyen: Non sono sicuro di cosa intendi; se ne hai la memoria, allora non sarebbe "rotto". –

5

Tutte le operazioni di I/O sono asincrone. Il thread attende (viene sospeso) per il completamento dell'operazione I/O. Ecco perché quando leggi jeffrey richter dice sempre di fare i/o asincrono, in modo che il tuo thread non venga sprecato aspettando in giro. da Jeffery Ricter

Anche la creazione di una discussione non è economica. Ogni thread riceve 1 mb di spazio di indirizzamento riservato per la modalità utente e un altro 12kb per la modalità kernel. Dopo questo il sistema operativo deve notificare a tutte le DLL nel sistema che è stato generato un nuovo thread. Lo stesso accade quando si distrugge un thread. Anche pensare la complessità del cambio di contesto

trovato un grande SO rispondere here