System configuration:
dotnet --version: 2.0.0I'm facing performance issues using File.WriteAllTextAsync in .NET core 2.0. I'm relatively new to .NET core, so I don't know if I'm doing something wrong here. For testing purposes, have written 3 methods that do the same thing in different ways: all of them write some content to a random file, and then delete it.
```c#
static async Task WriteAllTextAsync()
{
string fileName = System.Guid.NewGuid().ToString();
await File.WriteAllTextAsync($"write/{fileName}.txt", content);
File.Delete($"write/{fileName}.txt");
}
static void WriteAllText()
{
string fileName = System.Guid.NewGuid().ToString();
File.WriteAllText($"write/{fileName}.txt", content);
File.Delete($"write/{fileName}.txt");
}
static async Task WriteWithStreamWriter()
{
string fileName = System.Guid.NewGuid().ToString();
using (StreamWriter outputFile = new StreamWriter($"write/{fileName}.txt"))
{
await outputFile.WriteAsync(content);
}
File.Delete($"write/{fileName}.txt");
}
I am using the following loop to call these methods:
```c#
for (int i = 0; i < 200; ++i)
{
//"method()" or "await async_method()"
}
I've noticed that WriteAllTextAsync is significantly slower than the other methods and results in higher disk usage (no fancy benchmarks here, the HDD led on my pc just stays turned on when using this method). Using time on each method gives the following results:
real 0m14.579s
user 0m4.314s
sys 0m0.460s
WriteAllText
real 0m4.244s
user 0m4.116s
sys 0m0.418s
WriteWithStreamWriter
real 0m4.243s
user 0m4.133s
sys 0m0.432s
As expected, the results using File.WriteAllText (synchronously) and StreamWriter.WriteAsync (asynchronously) are very close, but File.WriteAllTextAsync is much slower.
the results using File.WriteAllText (synchronously) and StreamWriter.WriteAsync (asynchronously) are very close
How are you creating the stream in the case where you use StreamWriter.WriteAsync? Are you using the default of synchronous I/O or are you constructing the FileStream to use async I/O (the bool useAsync or FileOptions.Asynchronous ctor parameter).
File.WriteAllTextAsync will open the FileStream for async I/O:
https://github.com/dotnet/corefx/blob/master/src/System.IO.FileSystem/src/System/IO/File.cs#L744
My guess is you're simply seeing the performance overheads of async file I/O vs sync file I/O.
I'm not doing anything besides what is shown in WriteWithStreamWriter, so I guess it's the default stream. Actually, I've followed this guide to have StreamWriter write to a file asynchronously, so I thought WriteAsync would use async I/O by default. Is this not the case?
I thought the *Async methods always did async IO 馃樁
There's async at the API level and async from the OS' perspective.
FileStream has two modes, set when it's constructed, which dictates whether it opens the Windows file handle for overlapped I/O or not; this is because opening a file handle on Windows requires this to be dictated when the file is opened, not per operation. That's entirely separate from whether you then call synchronous or asynchronous methods on the FileStream. If you open the FileStream for synchronous I/O (the default), then calling the synchronous Read/Write methods do synchronous I/O, and calling the asynchronous Read/WriteAsync methods on the FileStream can't use overlapped I/O and thus just end up doing the base stream behavior of queueing thread pool work items to invoke the synchronous read/write methods. If instead you open the FileStream for asynchronous I/O (via the ctor taking useAsync=true or FileOptions.Asynchronous), then the synchronous Read/Write methods actually do asynchronous operations and block waiting for them to complete, and the asynchronous Read/WriteAsync methods do overlapped I/O.
Thank you for the explanation, I was not aware of this behavior.
However, I was not expecting such a difference in performance when running async I/O. Since you mentioned that there are some aspects of it particular to Windows, I've decided to test these methods on my Windows laptop. Here are the results using Measure-Command from Powershell:
TotalSeconds : 3,2205324
WriteAllText
TotalSeconds : 2,8658628
WriteWithStreamWriter
TotalSeconds : 2,884125
Indeed, on Windows, WriteAllTextAsync performs very well, with little overhead comparing to the test on Arch Linux. Is this behavior expected when doing async I/O on Linux?
@stephentoub Huh. My guess would have been that FileStream always did overlapped IO because otherwise you'd have issues using up threads, but I guess you don't!
@jnm2 Sorry, not sure if I get your point. The explanation given by @stephentoub that FileStream does synchronous I/O by default makes sense to me, since in this scenario, StreamWriter.WriteAsync would write synchronously in a dedicated thread. Because I'm always awaiting this method in my tests, it should perform almost the same as WriteAllText, but with some overhead caused by this additional thread. Thus, WriteAllTextAsync would be the only method performing asynchronous I/O. My only remaining question is whether the overhead I'm getting for this async I/O on Linux is expected, since it does not happen on Windows...
@fabio-muramatsu I'm sorry, was responding to Stephen. I'm also curious about your question.
@fabio-muramatsu, sorry, I missed that you were on Linux rather than on Windows.
On Unix, there isn't this dichotomy between file descriptors opened for sync vs async I/O. We do enforce some of the same behaviors visible on Windows, e.g. code on Windows expects to be able to use a FileStream opened with async I/O for concurrent writes (meaning while you're not allowed to access a FileStream concurrently from two different threads, you are allowed to issue from the same thread an async operation without waiting for the previous one to complete), so we use additional synchronization on Unix when FileStream is opened for async to enable that. But I would not expect a 3x slowdown as a result; generally I'd expect it to be negligable overhead, and it should otherwise be equivalent to the sync performance. I'll need to do some experiments.
Could this be the different "Flush" behavior between sync and async code paths?
WriteAllTextAsync does FlushAsync internally which calls fsync on Linux.
The other two methods call Flush implicitly in Dispose() with flushToDisk: false, so this code path doesn't fsync
Most helpful comment
There's async at the API level and async from the OS' perspective.
FileStream has two modes, set when it's constructed, which dictates whether it opens the Windows file handle for overlapped I/O or not; this is because opening a file handle on Windows requires this to be dictated when the file is opened, not per operation. That's entirely separate from whether you then call synchronous or asynchronous methods on the FileStream. If you open the FileStream for synchronous I/O (the default), then calling the synchronous Read/Write methods do synchronous I/O, and calling the asynchronous Read/WriteAsync methods on the FileStream can't use overlapped I/O and thus just end up doing the base stream behavior of queueing thread pool work items to invoke the synchronous read/write methods. If instead you open the FileStream for asynchronous I/O (via the ctor taking useAsync=true or FileOptions.Asynchronous), then the synchronous Read/Write methods actually do asynchronous operations and block waiting for them to complete, and the asynchronous Read/WriteAsync methods do overlapped I/O.