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();
}
Run Code Online (Sandbox Code Playgroud)
然后将其转换为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 ())
}
Run Code Online (Sandbox Code Playgroud)
然后我发现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<_>
}
Run Code Online (Sandbox Code Playgroud)
但表现基本相同.
作为性能的一个例子,当我加载一条市场数据时,例如:
type OHLC = {
Time : DateTime
Open : float
High : float
Low : float
Close : float
}
Run Code Online (Sandbox Code Playgroud)
在我的机器上,F#异步版本耗费了大约两倍的时间,并且在整个运行时消耗了〜两倍的CPU资源 - 因此占用了大约4倍的资源(即内部必须旋转更多的线程?).
(可能读取这样一个简单的结构有点可疑吗?我真的只是在机器上查看它的功能.与非异步版本(即直接读取)相比,c#one完成了〜同时,但消耗> CPU的两倍.即直接Read()消耗<f#资源的1/8)
所以我的问题是,当我以"正确"的方式进行F#异步时(这是我第一次尝试使用)?
(...如果我,那么我只需要去修改编译器为已编译的Asyncs添加基于状态机的实现......这有多难:-))
V.B*_*.B. 12
F#Async和TPL边界(Async.AwaitTask/Async.StartAsTask)是最慢的.但一般来说,F#Async本身较慢,应该用于IO绑定而不是CPU绑定任务.你可能会发现这个回购有趣:https://github.com/buybackoff/FSharpAsyncVsTPL
基本上,我对这两者以及任务构建器计算表达式进行了基准测试,该表达式最初来自FSharpx项目.与TPL一起使用时,任务构建器要快得多.我在我的Spreads库中使用这种方法 - 这是用F#编写的,但它利用了TPL.在这一行是高度优化的计算表达式绑定,它有效地做了与C#的async/await在幕后相同的事情.我task{}对库中计算表达式的每次使用进行了基准测试,并且速度非常快(不得使用for/while计算表达式,而是使用递归).此外,它使代码可以与C#互操作,而F#的异步不能从C#中消耗掉.