Search code examples
c#fileasynchronous.net-4.5

.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.


Solution

  • 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