Optimization 我怎样才能去掉核心中的“let”?

Optimization 我怎样才能去掉核心中的“let”?,optimization,haskell,core,Optimization,Haskell,Core,我有一个在内部循环中经常调用的函数。看起来是这样的: import qualified Data.Vector.Storable as SV newtype Timedelta = Timedelta Double cklsLogDens :: SV.Vector Double -> Timedelta -> Double -> Double -> Double cklsLogDens p (Timedelta dt) x0 x1 = if si <= 0 th

我有一个在内部循环中经常调用的函数。看起来是这样的:

import qualified Data.Vector.Storable as SV

newtype Timedelta = Timedelta Double

cklsLogDens :: SV.Vector Double -> Timedelta -> Double -> Double -> Double
cklsLogDens p (Timedelta dt) x0 x1 = if si <= 0 then -1e50 else c - 0.5*((x1-mu)/sd)^2 
  where
    al  = p `SV.unsafeIndex` 0
    be  = p `SV.unsafeIndex` 1
    si  = p `SV.unsafeIndex` 2
    xi  = p `SV.unsafeIndex` 3
    sdt = sqrt dt
    mu  = x0 + (al + be*x0)*dt
    sd  = si * (x0 ** xi) * sdt
    c   = sd `seq` -0.5 * log (2*pi*sd^2)
(是的,当然,因为你必须问,我在过早优化上花费了太多时间…)

这是NOINLINE的当前版本

import qualified Data.Vector.Storable as SV

newtype Timedelta = Timedelta Double

cklsLogDens :: SV.Vector Double -> Timedelta -> Double -> Double -> Double
{-# NOINLINE cklsLogDens #-}
cklsLogDens p (Timedelta dt) x0 x1 = si `seq` (if si <= 0 then -1e50 else (sd `seq` (c - 0.5*((x1-mu)/sd)^2)))
  where
    al  = p `SV.unsafeIndex` 0
    be  = p `SV.unsafeIndex` 1
    si  = p `SV.unsafeIndex` 2
    xi  = p `SV.unsafeIndex` 3
    sdt = sqrt dt
    mu  = x0 + (al + be*x0)*dt
    sd  = si * (x0 ** xi) * sdt
    c   = sd `seq` (-0.5 * log (2*pi*sd^2))

main = putStrLn . show $ cklsLogDens SV.empty (Timedelta 0.1) 0.1 0.15
导入符合条件的Data.Vector.Storable作为SV
newtype Timedelta=Timedelta-Double
cklsLogDens::SV.Vector Double->Timedelta->Double->Double->Double
{-#NOINLINE cklsLogDens}
cklsLogDens p(Timedelta dt)x0 x1=si`seq`(如果si Main.Timedelta
->GHC.Types.Double
->GHC.Types.Double
->GHC.Types.Double
[GblId,Arity=4,Caf=NoCafRefs,Str=DMD类型U(全部)LLL]
Main.cklsLogDens=
\(p_atw::Data.Vector.Storable.Vector GHC.Types.Double)
(ds_dVa::Main.Timedelta)
(x0_aty::GHC.Types.Double)
(x1_atz::GHC.Types.Double)->
案例p_atw
_{Data.Vector.Storable.Vector rb_a2ml rb1_a2mm rb2_a2mn->
案例GHC.Prim.readDoubleOffAddr#
@GHC.Prim.RealWorld rb1_a2mm 2 GHC.Prim.RealWorld#
of{({s2}a2nH,x}a2nI}->
案例GHC.Prim.touch#
@GHC.ForeignPtr.ForeignPtr内容rb2_a2mn s2_a2nH
默认值->
案例GHC.Prim。
案例x0{GHC.Types.D#x2_a13d->
案例GHC.Prim.readDoubleOffAddr#
@GHC.Prim.RealWorld rb1_a2mm 3 GHC.Prim.RealWorld#
of{({s1{ux2ob,x3{ux2od})->
案例GHC.Prim.touch#
@GHC.ForeignPtr.ForeignPtr内容rb2\u a2mn s1\u X2oB
默认值->
案例D_dVa
`cast`(Main.NTCo:Timedelta::Main.Timedelta~#GHC.Types.Double)
关于{GHC.Types.D#x4_a13m->
让{
---^^^^^想摆脱这个!
---
ipv_sYP[Dmd=Just L]::GHC.Prim.Double#
[LclId,Str=DmdType]
ipv_sYP=
GHC.Prim*##
(GHC.Prim.*.#x#u a2nI(GHC.Prim.*.#x2#u a13d x3_X2oD))
(GHC.Prim.sqrtDouble#x4_a13m)英寸
_{GHC.Types.D#x5_X14E->
案例GHC.Prim.readDoubleOffAddr#
@GHC.Prim.RealWorld rb1_a2mm 0 GHC.Prim.RealWorld#
of{(#s3x2p2,x6x2p4)->
案例GHC.Prim.touch#
@GHC.ForeignPtr.ForeignPtr内容rb2\u a2mn s3\u X2p2
默认值->
案例GHC.Prim.readDoubleOffAddr#
@GHC.Prim.RealWorld RB1A2mm 1 GHC.Prim.RealWorld#
of{(#s4x2pi,x7x2pk)->
案例GHC.Prim.touch#
@GHC.ForeignPtr.ForeignPtr内容rb2_a2mn s4_X2pi
默认值->
案例GHC.Prim.logDouble#
(GHC.Prim.*.##6.283185307179586(GHC.Prim.*.##ipv#sYP ipv#sYP))
野生9_a13D{u_默认->
案例GHC.Prim/##
(GHC.Prim-##
x5_X14E
(GHC.Prim+##
x2_a13d
(GHC.Prim*##
(GHC.Prim.+##x6_X2p4(GHC.Prim.*.#x7_x2pkx2_a13d))x4_a13m)
ipv_sYP
野生10_a13O{{u__默认->
GHC.D类#
(GHC.Prim-##
(GHC.Prim.negateDouble#(GHC.Prim.*.##0.5 wild9_a13D))
(GHC.Prim.*.###0.5(GHC.Prim.*.####wild10#uA13o wild10#uA13o)))
}
}
}
}
}
}
}
}
}
}
};
GHC.Types.True->lvl_r2v7
}
}
}
}

使用ghc-7.6.1,我在
-O
-O2
之间没有区别,任何
seq
或bang模式也没有区别。让
留在核心

但是我怀疑
let
是否真的有害,它绑定了一个原始值,而不是一个装箱的值,并且该值在此后的三个位置被使用。此外,在生成的程序集中,我找不到任何懒洋洋的thunk的迹象(但由于我对程序集的了解非常有限,所以不要把它当作福音)

通过引入案例分支,我可以摆脱
let

cklsLogDens p (Timedelta dt) x0 x1
    = case p `SV.unsafeIndex` 2 of
        si | si <= 0   -> -1e50
           | otherwise ->
                let al  = p `SV.unsafeIndex` 0
                    be  = p `SV.unsafeIndex` 1
                    xi  = p `SV.unsafeIndex` 3
                    sdt = sqrt dt
                    mu  = x0 + (al + be*x0)*dt
                in case si*(x0**xi)*sdt of
                     0   -> 0
                     sd -> -0.5*log (2*pi*sd^2) - 0.5*((x1-mu)/sd)^2
cklsLogDens p(时间增量dt)x0 x1
=案例p`SV.unsafeIndex`2
si | si-1e50
|否则->
设al=p`SV.unsafeIndex`0
be=p`SV.unsafeIndex`1
席=P’sv,unSeaSt指标’3
sdt=sqrt dt
mu=x0+(al+be*x0)*dt
如果si*(x0**xi)*的sdt
0   -> 0
sd->-0.5*对数(2*pi*sd^2)-0.5*((x1μ)/sd)^2
它只在核心中产生
case
s。因为
sd
永远不应该是0,在循环中,即使是一个普通的分支预测器也应该使该分支基本上是自由的


但是,我怀疑这是否真的会提高性能。与0相比,需要一个寄存器,原始程序集生成的程序集需要更少的间接寻址,并且可以在需要时在寄存器中保留更多的值。

Daniel是对的-所讨论的
let
实际上没有分配thunk。这会起作用实际上这是不可能的,因为诸如
Double#
之类的基本类型没有堆表示。这些
let
s实际上在转换为STG之前转换为
case
表达式(这就是“
let
=allocation”规则实际适用的地方)在所谓的核心准备阶段。请参阅中有关此主题的评论

下面是准备之前的核心部分(
-ddump siml
):

下面是(
-ddump prep
):

所以实际上没有任何堆分配


另一方面,请注意,core preparation还显式地将每个应用程序包装到
let
case
语句中,生成非常详细的代码。这就是为什么
-ddump Siml
可能被认为是查看core的默认值,尽管其性能模型实际上稍微令人惊讶

不将
sd`seq`
移动到
else
分支
Main.cklsLogDens [InlPrag=NOINLINE]
  :: Data.Vector.Storable.Vector GHC.Types.Double
     -> Main.Timedelta
     -> GHC.Types.Double
     -> GHC.Types.Double
     -> GHC.Types.Double
[GblId, Arity=4, Caf=NoCafRefs, Str=DmdType U(ALL)LLL]
Main.cklsLogDens =
  \ (p_atw :: Data.Vector.Storable.Vector GHC.Types.Double)
    (ds_dVa :: Main.Timedelta)
    (x0_aty :: GHC.Types.Double)
    (x1_atz :: GHC.Types.Double) ->
    case p_atw
    of _ { Data.Vector.Storable.Vector rb_a2ml rb1_a2mm rb2_a2mn ->
    case GHC.Prim.readDoubleOffAddr#
           @ GHC.Prim.RealWorld rb1_a2mm 2 GHC.Prim.realWorld#
    of _ { (# s2_a2nH, x_a2nI #) ->
    case GHC.Prim.touch#
           @ GHC.ForeignPtr.ForeignPtrContents rb2_a2mn s2_a2nH
    of _ { __DEFAULT ->
    case GHC.Prim.<=## x_a2nI 0.0 of _ {
      GHC.Types.False ->
        case x0_aty of _ { GHC.Types.D# x2_a13d ->
        case GHC.Prim.readDoubleOffAddr#
               @ GHC.Prim.RealWorld rb1_a2mm 3 GHC.Prim.realWorld#
        of _ { (# s1_X2oB, x3_X2oD #) ->
        case GHC.Prim.touch#
               @ GHC.ForeignPtr.ForeignPtrContents rb2_a2mn s1_X2oB
        of _ { __DEFAULT ->
        case ds_dVa
             `cast` (Main.NTCo:Timedelta :: Main.Timedelta ~# GHC.Types.Double)
        of _ { GHC.Types.D# x4_a13m ->
        let {
   --- ^^^^ want to get rid of this!
   ---
          ipv_sYP [Dmd=Just L] :: GHC.Prim.Double#
          [LclId, Str=DmdType]
          ipv_sYP =
            GHC.Prim.*##
              (GHC.Prim.*## x_a2nI (GHC.Prim.**## x2_a13d x3_X2oD))
              (GHC.Prim.sqrtDouble# x4_a13m) } in
        case x1_atz of _ { GHC.Types.D# x5_X14E ->
        case GHC.Prim.readDoubleOffAddr#
               @ GHC.Prim.RealWorld rb1_a2mm 0 GHC.Prim.realWorld#
        of _ { (# s3_X2p2, x6_X2p4 #) ->
        case GHC.Prim.touch#
               @ GHC.ForeignPtr.ForeignPtrContents rb2_a2mn s3_X2p2
        of _ { __DEFAULT ->
        case GHC.Prim.readDoubleOffAddr#
               @ GHC.Prim.RealWorld rb1_a2mm 1 GHC.Prim.realWorld#
        of _ { (# s4_X2pi, x7_X2pk #) ->
        case GHC.Prim.touch#
               @ GHC.ForeignPtr.ForeignPtrContents rb2_a2mn s4_X2pi
        of _ { __DEFAULT ->
        case GHC.Prim.logDouble#
               (GHC.Prim.*## 6.283185307179586 (GHC.Prim.*## ipv_sYP ipv_sYP))
        of wild9_a13D { __DEFAULT ->
        case GHC.Prim./##
               (GHC.Prim.-##
                  x5_X14E
                  (GHC.Prim.+##
                     x2_a13d
                     (GHC.Prim.*##
                        (GHC.Prim.+## x6_X2p4 (GHC.Prim.*## x7_X2pk x2_a13d)) x4_a13m)))
               ipv_sYP
        of wild10_a13O { __DEFAULT ->
        GHC.Types.D#
          (GHC.Prim.-##
             (GHC.Prim.negateDouble# (GHC.Prim.*## 0.5 wild9_a13D))
             (GHC.Prim.*## 0.5 (GHC.Prim.*## wild10_a13O wild10_a13O)))
        }
        }
        }
        }
        }
        }
        }
        }
        }
        }
        };
      GHC.Types.True -> lvl_r2v7
    }
    }
    }
    }
cklsLogDens p (Timedelta dt) x0 x1
    = case p `SV.unsafeIndex` 2 of
        si | si <= 0   -> -1e50
           | otherwise ->
                let al  = p `SV.unsafeIndex` 0
                    be  = p `SV.unsafeIndex` 1
                    xi  = p `SV.unsafeIndex` 3
                    sdt = sqrt dt
                    mu  = x0 + (al + be*x0)*dt
                in case si*(x0**xi)*sdt of
                     0   -> 0
                     sd -> -0.5*log (2*pi*sd^2) - 0.5*((x1-mu)/sd)^2
    let {
      ipv_sPL [Dmd=Just L] :: GHC.Prim.Double#
      ipv_sPL =
        GHC.Prim.*##
          (GHC.Prim.*## x_a160 (GHC.Prim.**## x1_a11G x2_X17h))
          (GHC.Prim.sqrtDouble# x3_a11P) } in [...]
    case GHC.Prim.sqrtDouble# x3_s1aU of sat_s1cB { __DEFAULT ->
    case GHC.Prim.**## x1_s1aQ x2_s1aR of sat_s1cC { __DEFAULT ->
    case GHC.Prim.*## x_s1aC sat_s1cC of sat_s1cD { __DEFAULT ->
    case GHC.Prim.*## sat_s1cD sat_s1cB of ipv_s1aW [Dmd=Just L] { __DEFAULT ->