Search code examples
c#async-awaitstreamwriter.net-4.6.2

StreamWriter: (Write+Flush)Async seem much slower then synchronous variants


I was trying to find an IO bottleneck in my class and surprisingly noticed that sw.Write("m"); sw.Flush() can be 20000 faster then await sw.WriteAsync("m"); Await sw.FlushAsync(); when writing 1000000 messages to a file. Does, by any chance, anyone know why? My bet is StreamWriter's constructor taking a String does not parametrize a stream for async usage.

The code below can be launched from C# interactive. Yes, it's not the best place to measure the speed but it will show the matter at hand anyway:

var sr = new StreamWriter("G:\\file.file");
var N = 1000;
var sw = new Stopwatch();
sw.Start();

for (var i = 0; i < N; ++i)
{
    sr.Write("m"); // or await sr.WriteAsync("m");
    sr.Flush(); // or await sr.FlushAsync("m");
}

sw.Stop();
Console.WriteLine("Completed " + N
    + " iterations in " + sw.ElapsedMilliseconds + " milliseconds.");

sr.Close();

Launched on my home PC from C# Interactive the output is

Completed 1000 iterations in 1 milliseconds.

for synchronous code and

Completed 1000 iterations in 43383 milliseconds.

for asynchronous.

Update: Also I've noticed that the program slows down inside FlushAsync. WriteAsync works nearly at the same speed as the synchronous version.

All comments are welcome.

Update 2*: As @Cory Nelson mentioned in comments to his answer, FileStream.FlushAsync is a fake async that is implemented with Task.Factory.StartNew, so it adds nothing useful but overhead. When working with short messages the overhead becomes large enough compared to the work being done, thus slowing the execution.


Solution

  • StreamWriter, when given a file path, does not open its underlying stream in async mode. This is likely contributing to performance loss. If you're going to use it for async, you should be opening your own Stream:

    Stream s = new FileStream("G:\\file.file", FileMode.Create, FileAccess.Write,
                              FileShare.None, 4096,
                              FileOptions.Asynchronous | FileOptions.SequentialScan);
    StreamWriter sr = new StreamWriter(s);
    

    Something additional to note is that your benchmark doesn't seem to capture real-world usage. Are you really writing single-character strings and flushing after each one?

    Async does have a certain fixed amount of memory and GC overhead, and with such short-lived operations -- especially as StreamWriter.WriteAsync is not currently optimized for small writes -- you are bound to see much more overhead than a more common usage.