Reputation: 2586
While being good for thread hygiene I expected asynchronous I/O to always be a little slower than synchronous I/O. My tests seem to prove that async I/O sometimes is faster than sync. What am I missing here?
[EDIT] My initial measurements were wrong (I did not delete them to not invalidate comments made).
Here are some measurements with the timing loop fixed: ADO_DataReaderSync per iteration=4,17ms ADO_DataReaderASyncReader per iteration=3,55ms (only ExecuteReaderAsync) ADO_DataReaderASyncRead per iteration=11,28ms (ExecuteReaderAsync and ReadAsync) FileIO_ReadToEndSync SmallFile per iteration=3,67ms FileIO_ReadToEndAsync SmallFile per iteration=8,97ms FileIO_ReadToEndSync LargeFile per iteration=266,34ms FileIO_ReadToEndAsync LargeFile per iteration=322,05ms
BUGGY MEASUREMENTS:
ADO_ReadSync elapsed:00:00:00.0012249 per iteration=0,12249ms ADO_ReadAsync elapsed:00:00:00.0050702 per iteration=0,50702ms ADO_DataReaderSync elapsed:00:00:00.0090513 per iteration=0,90513ms ADO_DataReaderASync elapsed:00:00:00.0044125 per iteration=0,44125ms FileIO_ReadSync LargeFile elapsed:00:00:00.0655596 per iteration=6,55596ms FileIO_ReadAsync LargeFile elapsed:00:00:00.0003056 per iteration=0,03056ms FileIO_ReadSync SmallFile elapsed:00:00:00.0005619 per iteration=0,05619ms FileIO_ReadAsync SmallFile elapsed:00:00:00.0002955 per iteration=0,02955ms
Test code used:
Module Module1
Private Const _connectionString = "Data Source=zulu;Initial Catalog=AdventureWorks;Integrated Security=True"
Sub Main()
DoTimed("ADO_ReadSync", Sub() ADO_ScalarSync(), 10)
DoTimed("ADO_ReadAsync", Async Sub() Await ADO_ScalarAsync(), 10)
DoTimed("ADO_DataReaderSync", Sub() ADO_DataReaderSync(), 10)
DoTimed("ADO_DataReaderASync", Async Sub() Await ADO_DataReaderASync(), 10)
Const filePathLargeFile = "O:\Temp\TestFiles\In\todo.txt"
Const filePathSmallFile = "O:\Temp\TestFiles\In\eula.txt"
DoTimed("FileIO_ReadSync LargeFile", Sub() FileIO_ReadSync(filePathLargeFile), 10)
DoTimed("FileIO_ReadAsync LargeFile", Async Sub() Await FileIO_ReadAsync(filePathLargeFile), 10)
DoTimed("FileIO_ReadSync SmallFile", Sub() FileIO_ReadSync(filePathSmallFile), 10)
DoTimed("FileIO_ReadAsync SmallFile", Async Sub() Await FileIO_ReadAsync(filePathSmallFile), 10)
Console.WriteLine("...")
Console.ReadLine()
End Sub
Function ADO_ScalarSync() As Integer
Using cnx As New SqlClient.SqlConnection(_connectionString)
Dim cmd As New SqlCommand("SELECT COUNT(*) FROM Production.Product", cnx)
cnx.Open()
Return cmd.ExecuteScalar
End Using
End Function
Async Function ADO_ScalarAsync() As Task(Of Integer)
'Beginning in the .NET Framework 4.5 RC, these methods no longer require Asynchronous Processing=true in the connection string
Using cnx As New SqlClient.SqlConnection(_connectionString)
Dim cmd As New SqlCommand("SELECT COUNT(*) FROM Production.Product", cnx)
cnx.Open()
Return Await cmd.ExecuteScalarAsync
End Using
End Function
Function ADO_DataReaderSync() As List(Of String)
Using cnx As New SqlClient.SqlConnection(_connectionString)
Dim cmd As New SqlCommand("SELECT * FROM Production.Product", cnx)
cnx.Open()
Using rdr As SqlDataReader = cmd.ExecuteReader
Dim productNames As New List(Of String)
While rdr.Read
productNames.Add(rdr("Name"))
End While
Return productNames
End Using
End Using
End Function
Async Function ADO_DataReaderASync() As Task(Of List(Of String))
Using cnx As New SqlClient.SqlConnection(_connectionString)
'Await cnx.OpenAsync() 'I would only use .OpenAsync if the DB is commonly down and we would hang on the timeout
Dim cmd As New SqlCommand("SELECT * FROM Production.Product", cnx)
cnx.Open()
Using rdr As SqlDataReader = Await cmd.ExecuteReaderAsync
Dim productNames As New List(Of String)
While rdr.Read
productNames.Add(rdr("Name"))
End While
Return productNames
End Using
End Using
End Function
Function FileIO_ReadSync(filePath As String) As Long
Using reader As New StreamReader(filePath)
Dim fileString = reader.ReadToEnd
Return fileString.Length
End Using
End Function
Async Function FileIO_ReadAsync(filePath As String) As Task(Of Long)
Using reader As New StreamReader(filePath)
Dim fileString = Await reader.ReadToEndAsync().ConfigureAwait(False)
Return fileString.Length
End Using
End Function
End Module
Public Module Timing
Function DoTimed(name As String, operation As Action, Optional iterations As Integer = 1) As TimeSpan
operation() 'Warmup
Dim stw = Stopwatch.StartNew
DoIterate(operation, iterations)
stw.Stop()
Console.WriteLine("{0} elapsed:{1} per iteration={2}ms", name, stw.Elapsed, stw.Elapsed.TotalMilliseconds / iterations)
Return stw.Elapsed
End Function
Sub DoIterate(action As Action, Optional iterations As Integer = 1)
For i = 0 To iterations - 1
action()
Next
End Sub
End Module
Upvotes: 1
Views: 1423
Reputation: 881103
Depends on your definition of slower :-)
It's possible that async I/O will be slower simply because it has to be run in a separate thread of execution which takes time to set up - this of course depends on the implementation. Even if the threads are already running for async I/O such as when you've set them up by using I/O completion ports, the time taken to communicate the information between threads may be a factor.
However, the fact that you can continue doing other things while waiting for the I/O to finish means that the total time taken for all operations may well be less.
So, while the I/O may be slower, the overall time taken for all operations will generally be better.
In the cases where async seems to be faster, that could be an incredibly efficient implementation, caching of information or a dozen other possibilities, one of which may be a faulty benchmark on your part.
Presumably you're using async because you want to do something else (including possibly keeping a GUI thread responsive). If so, it doesn't really matter how slow it is (within reason).
Upvotes: 3
Reputation: 456332
Your timing loop is not waiting for the asynchronous operations to complete, so it's just timing how long it takes to start them.
"Proving" anything using microbenchmarking is quite difficult.
Upvotes: 2