F#与C#中异步的性能(是否有更好的方法来编写异步{...})

时间:2016-03-03 03:40:17

标签: f# async-await

FWIW我认为这里详述的问题仅归结为c#编译器更智能,并使一个有效的基于状态机的模型来处理异步代码,而F#编译器创建了无数的对象和函数调用效率低下。

无论如何,如果我有以下c#功能:

public async static Task<IReadOnlyList<T>> CSharpAsyncRead<T>(
         SqlCommand cmd,
         Func<SqlDataReader, T> createDatum)
{
    var result = new List<T>();
    var reader = await cmd.ExecuteReaderAsync();

    while (await reader.ReadAsync())
    {
        var datum = createDatum(reader);
        result.Add(datum);
    }

    return result.AsReadOnly();
}

然后将其转换为F#,如下所示:

let fsharpAsyncRead1 (cmd:SqlCommand) createDatum = async {
    let! reader =
        Async.AwaitTask (cmd.ExecuteReaderAsync ())

    let rec readRows (results:ResizeArray<_>) = async {
        let! readAsyncResult = Async.AwaitTask (reader.ReadAsync ())
        if readAsyncResult then
            let datum = createDatum reader
            results.Add datum
            return! readRows results
        else
            return results.AsReadOnly() :> IReadOnlyList<_>
    }

    return! readRows (ResizeArray ())
}

然后我发现f#代码的性能明显慢于c#版本,并且CPU耗得更多。我想知道是否有更好的组成。我尝试删除递归函数(看起来有点难看,而不是!而且没有可变的!),如下所示:

let fsharpAsyncRead2 (cmd:SqlCommand) createDatum = async {
    let result = ResizeArray () 

    let! reader =
        Async.AwaitTask (cmd.ExecuteReaderAsync ())

    let! moreData = Async.AwaitTask (reader.ReadAsync ())
    let mutable isMoreData = moreData
    while isMoreData do
        let datum = createDatum reader

        result.Add datum

        let! moreData = Async.AwaitTask (reader.ReadAsync ())
        isMoreData <- moreData

    return result.AsReadOnly() :> IReadOnlyList<_>
}

但表现基本相同。

作为性能的一个例子,当我加载一系列市场数据时,例如:

type OHLC = {
    Time  : DateTime
    Open  : float
    High  : float
    Low   : float
    Close : float
}

在我的机器上,F#异步版本需要大约两倍的时间,并且在整个运行过程中消耗了〜两倍的CPU资源 - 因此占用了大约4倍的资源(即内部必须旋转更多的线程? )。

(可能读取这样一个简单的结构有点可疑吗?我真的只是在机器上查看它的作用。与非异步版本相比(即直接阅读) c#one在同一时间内完成,但耗费了两倍的CPU。即直接读取()消耗&lt; 1/8 f#资源“

所以我的问题是,当我做F#异步时,&#34;对&#34;方式(这是我第一次尝试使用)?

(...如果我,那么我只需要修改编译器为编译的Asyncs添加基于状态机的实现......这有多难:-))

1 个答案:

答案 0 :(得分:12)

F#的Async和TPL边界(Async.AwaitTask / Async.StartAsTask)是最慢的。但一般来说,F#Async本身较慢,应该用于IO绑定而不是CPU绑定任务。您可能会发现此回购有趣:N is the node "person"

基本上,我对两者以及任务构建器计算表达式进行了基准测试,该表达式最初来自https://github.com/buybackoff/FSharpAsyncVsTPL项目。与TPL一起使用时,任务构建器要快得多。我在我的Spreads库中使用这种方法 - 这是用F#编写的,但它利用了TPL。在FSharpx上是高度优化的计算表达式绑定,它有效地做了与C#的async / await在幕后相同的事情。我对库中task{}计算表达式的每次使用进行了基准测试,并且速度非常快(这个问题不是用于/计算表达式,而是递归)。此外,它使代码可以与C#互操作,而F#的异步不能从C#中消耗。