Lectura de archivos .NET 4.5 rendimiento sincronizado vs asíncrono

Estamos tratando de medir el rendimiento entre leer una serie de archivos usando métodos de sincronización vs asíncronos. Esperaba tener aproximadamente el mismo tiempo entre los dos, pero resulta que usar async es aproximadamente 5.5x más lento.

Esto podría deberse a la sobrecarga de administrar los subprocesos, pero solo quería saber su opinión. Tal vez solo estamos midiendo los tiempos mal.

Estos son los métodos que se están probando:

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

Y este es el método que los ejecuta y comienza el cronómetro:

  static void Test(string name, Func 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); } 

Que se ejecuta todo desde aquí:

  static void Main(string[] args) { int count = 10000; for (int i = 0; i  Task.Run(() => ReadAllFile(filename)), count); Test("Read Contents Async", (filename) => ReadAllFileAsync(filename), count); Console.ReadKey(); } 

Y el método de escritura auxiliar:

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

Los resultados:

 -Read Contents 574 ms -Read Contents Async 3160 ms 

Realmente apreciaremos si alguien puede arrojar algo de luz sobre esto mientras buscamos en la stack y en la web, pero no podemos encontrar una explicación adecuada.

Hay muchas cosas mal con el código de prueba. En particular, su prueba “asíncrona” no usa E / S asíncrona; con los flujos de archivos, tiene que abrirlos explícitamente como asíncronos o, de lo contrario, solo está realizando operaciones síncronas en un subproceso en segundo plano. Además, los tamaños de sus archivos son muy pequeños y se pueden almacenar fácilmente en caché.

Modifiqué el código de prueba para escribir archivos mucho más grandes, para tener un código de sincronización contra código asíncrono comparable, y para hacer el código asíncrono asíncrono:

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

En mi máquina, esta prueba (integrada en Release, ejecutada fuera del depurador) produce estos números:

 Sync: 00:00:00.4461936 Async: 00:00:00.4429566 

Todas las operaciones de E / S son asíncronas. El hilo solo espera (se suspende) a que finalice la operación de E / S. Es por eso que cuando lee a Jeffrey Richter, siempre le dice que haga i / o async, para que su hilo no se desperdicie esperando. de Jeffery Ricter

También crear un hilo no es barato. Cada hilo recibe 1 mb de espacio de direcciones reservado para el modo de usuario y otro 12kb para el modo de núcleo. Después de esto, el sistema operativo debe notificar a todos los archivos DLL en el sistema que se ha generado un nuevo subproceso. Sucede lo mismo cuando destruyes un subproceso. También piensa en las complejidades del cambio de contexto.

Encontré una gran respuesta de SO aquí