Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET 4.5 file read performance sync vs async

We're trying to measure the performance between reading a series of files using sync methods vs async. Was expecting to have about the same time between the two but turns out using async is about 5.5x slower.

This might be due to the overhead of managing the threads but just wanted to know your opinion. Maybe we're just measuring the timings wrong.

These are the methods being tested:

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

And this is the method that runs them and starts the stopwatch:

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

Which is all run from here:

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

And the helper write method:

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

The results:

-Read Contents 574 ms -Read Contents Async 3160 ms 

Will really appreciate if anyone can shed some light on this as we searched the stack and the web but can't really find a proper explanation.

like image 350
gcastelo Avatar asked Aug 20 '13 09:08

gcastelo


People also ask

Should I use async in Web API?

In general, you should make a method asynchronous if the synchronous method blocks the ASP.NET request thread while doing no work. By making the call asynchronous, the ASP.NET request thread is not blocked doing no work while it waits for the web service request to complete.

Is C# a sync or async?

C# supports both synchronous and asynchronous methods. Let's learn the difference between synchronous and asynchronous and how to code in C#. Interestingly enough, any method we normally create in C# is synchronous by default.

Why would you use asynchronous actions?

Asynchronous is a non-blocking architecture, which means it doesn't block further execution while one or more operations are in progress. With asynchronous programming, multiple related operations can run concurrently without waiting for other tasks to complete.

Does async await block main thread C#?

Think of “await” as an “asynchronous wait”. The async method pauses until the awaitable is complete (so it waits), but the actual thread is not blocked (so it's asynchronous).


2 Answers

There are lots of things wrong with the testing code. Most notably, your "async" test does not use async I/O; with file streams, you have to explicitly open them as asynchronous or else you're just doing synchronous operations on a background thread. Also, your file sizes are very small and can be easily cached.

I modified the test code to write out much larger files, to have comparable sync vs async code, and to make the async code asynchronous:

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

On my machine, this test (built in Release, run outside the debugger) yields these numbers:

Sync: 00:00:00.4461936 Async: 00:00:00.4429566 
like image 117
Stephen Cleary Avatar answered Sep 27 '22 03:09

Stephen Cleary


All I/O Operation are async. The thread just waits(it gets suspended) for I/O operation to finish. That's why when read jeffrey richter he always tells to do i/o async, so that your thread is not wasted by waiting around. from Jeffery Ricter

Also creating a thread is not cheap. Each thread gets 1 mb of address space reserved for user mode and another 12kb for kernel mode. After this the OS has to notify all the dll in system that a new thread has been spawned.Same happens when you destroy a thread. Also think about the complexities of context switching

Found a great SO answer here

like image 41
Anand Avatar answered Sep 23 '22 03:09

Anand