在F#社区中,众所周知,PowerPack的报价编译工具会生成非常慢的代码,而实际上是如此之慢,以至于它的性能甚至比单纯的解释还差。我一直在调查造成这种情况的原因,但到目前为止,我还没有找到令人信服的答案。有人声称,发生这种情况的原因是由于报价中的模式匹配之类的东西的表示效率低下,或者是由于该库使用的表达式树固有的效率低下。我想用一个简单的例子说明为什么我认为都不是正确的:


#r "FSharp.Powerpack.Linq.dll"

open System
open System.Linq.Expressions

open Microsoft.FSharp.Quotations.Patterns

let powerpack = Microsoft.FSharp.Linq.QuotationEvaluator.Compile <@ 1 + 1 @>

// explicitly rewrite above quotation with expression trees
let expressionTree =
    let (Call(_,addM,_)) = <@ 1 + 1 @>
    let constExpr (x : 'T) = Expression.Constant(box x, typeof<'T>)
    let eval = Expression.Call(addM, constExpr 1, constExpr 1)
    let lambda = Expression.Lambda<Func<int>>(eval)
    lambda.Compile()

// reflection - based evaluation
let reflection =
    let (Call(_,addM,_)) = <@ 1 + 1 @>
    fun () -> addM.Invoke(null, [| 1 :> obj ; 1 :> obj |]) :?> int

#time

// QuotationEvaluator ~ 2.5 secs
for i in 1 .. 1000000 do
    powerpack () |> ignore

// native evaluation ~ 1 msec
for i in 1 .. 1000000 do
    (fun () -> 1 + 1) () |> ignore

// reflection evaluation ~ 700 msec
for i in 1 .. 1000000 do
    reflection () |> ignore

// naive expression tree ~ 19 msec
for i in 1 .. 1000000 do
    expressionTree.Invoke () |> ignore

这里显然出了点问题。问题是,什么?

编辑:FSharpx.Linq编译器也会发生相同的行为

最佳答案

下面是编译的实现:

let CompileImpl (e: #Expr, eraseEquality) =
       let ty = e.Type
       let e = Expr.NewDelegate(GetFuncType([|typeof<unit>; ty |]), [new Var("unit",typeof<unit>)],e)
       let linqExpr = Conv (e,eraseEquality)
       let linqExpr = (linqExpr :?> LambdaExpression)
       let d = linqExpr.Compile()
       (fun () ->
           try
             d.DynamicInvoke [| box () |]
           with :? System.Reflection.TargetInvocationException as exn ->
               raise exn.InnerException)

请注意,在委托(delegate)上使用了DynamicInvoke,这比Invoke慢得多,这是得到结果的原因。

关于f# - 有人知道QuotationEvaluator这么慢的原因吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/18298691/

10-11 07:27