1

虽然有利于线程卫生,但我希望异步 I/O 总是比同步 I/O 慢一点。我的测试似乎证明异步 I/O 有时比同步更快。我在这里想念什么?

[编辑] 我最初的测量是错误的(我没有删除它们以不使所做的评论无效)。

以下是一些固定定时循环的测量结果:
ADO_DataReaderSync 每次迭代=4,17ms
ADO_DataReaderASyncReader 每次迭代=3,55ms(仅 ExecuteReaderAsync)
ADO_DataReaderASyncRead 每次迭代=11,28ms(ExecuteReaderAsync 和 ReadAsync)
FileIO_ReadToEndSync SmallFile 每次迭代=3,67ms
FileIO_ReadToEndAsync SmallFile 每次迭代=8,97ms
FileIO_ReadToEndSync LargeFile 每次迭代=266,34ms
FileIO_ReadToEndAsync LargeFile 每次迭代=322,05ms

错误测量:

ADO_ReadSync elapsed:00:00:00.0012249 每次迭代=0,12249ms
ADO_ReadAsync elapsed:00:00:00.0050702 每次迭代=0,50702ms
ADO_DataReaderSync elapsed:00:00:00.0090513 每次迭代=0,90513ms
ADO_DataReaderASync elapsed:00:00:00.0044125 每次迭代=0,44125ms
FileIO_ReadSync LargeFile elapsed:00:00:00.0655596 每次迭代=6,55596ms
FileIO_ReadAsync LargeFile elapsed:00:00:00.0003056 每次迭代=0,03056ms
FileIO_ReadSync SmallFile elapsed:00:00:00.0005619 每次迭代=0,05619ms
FileIO_ReadAsync SmallFile elapsed:00:00:00.0002955 每次迭代=0,02955ms

使用的测试代码:

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

端模块

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

端模块

4

2 回答 2

3

取决于您对较慢的定义:-)

异步 I/O 可能会更慢,因为它必须在单独的执行线程中运行,这需要时间来设置——这当然取决于实现。即使线程已经在为异步 I/O 运行,例如当您使用 I/O 完成端口设置它们时,线程之间传递信息所花费的时间也可能是一个因素。

但是,您可以在等待 I/O 完成的同时继续做其他事情,这意味着所有操作所花费的时间可能会更少。

因此,虽然 I/O 可能较慢,但所有操作所花费的总时间通常会更好。

在异步似乎更快的情况下,这可能是一种非常有效的实现、信息缓存或其他十几种可能性,其中一种可能是您的基准错误。

大概您正在使用异步,因为您想做其他事情(包括可能保持 GUI 线程响应)。如果是这样,它有多慢并不重要(在合理范围内)。

于 2012-09-22T12:01:22.453 回答
2

您的计时循环不会等待异步操作完成,所以它只是计时启动它们需要多长时间。

使用微基准测试“证明”任何东西都非常 困难

于 2012-09-22T12:29:55.823 回答