haskell - 当存在一些递归情况时保持内联的潜力

标签 haskell optimization ghc

考虑以下数据类型,它只是为了说明:

data D where
  D1 :: Int -> D
  D2 :: String -> D
  DJ :: D -> D -> D

也许还有一个函数,比如 toString:

{-# INLINE toString #-}
toString x = case x of
  (D1 x) -> "Int: show x"
  (D2 x) -> "String: show x"
  (DJ x y) -> "(" ++ toString x ++ "," ++ toString y ++ ")"

(值得注意的是我所做的与打印无关,这只是一个说明性示例)

所以我发现通过像这样定义 toString 可以让我的程序快 15 倍:

{-# INLINE toString #-}
toString x = case x of
  (D1 x) -> "Int: show x"
  (D2 x) -> "String: show x"
  (DJ x y) -> undefined

发生的事情是 toString 现在能够被 GHC 内联。这允许在未来进行大量优化。 DJ 案例是导致问题的原因。所以我尝试了这个:

{-# INLINE toString #-}
toString x = case x of
  (D1 x) -> intShow x
  (D2 x) -> strShow x
  _ -> go x
  where
    go (D1 x) -> intShow x
    go (D2 x) -> strShow x
    go (DJ x y) -> "(" ++ go x ++ "," ++ go y ++ ")"
    intShow x = "Int: show x"
    strShow x = "String: show x"

这实际上意味着它编译速度很快。原因是(我很确定)因为 toString 不再是递归的。 go 是,但 toString 不是。所以编译器会很高兴地内联 toString,允许更多的优化。

但在我看来,上面的代码很丑。

就像我说的,我拥有的功能比这更复杂,而且这种问题在我的代码中出现。我有一个包含很多构造函数的数据类型,有些简单,有些递归。然而,每当我定义一个递归案例时,它甚至会减慢简单的案例。有没有办法保持顶级函数内联而不像我上面那样修改代码?

最佳答案

我没有优雅的解决方案,但也许这样的方法可行。未经测试。

{-# INLINE toString #-}
toString x = go (fix go)  -- equivalent to (fix go), but unrolled once
  where
  {-# INLINE go #-}
  go _ (D1 x) -> intShow x
  go _ (D2 x) -> strShow x
  go k (DJ x y) -> "(" ++ k x ++ "," ++ k y ++ ")"
  intShow x = "Int: show x"
  strShow x = "String: show x"

关于haskell - 当存在一些递归情况时保持内联的潜力,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42222998/

相关文章:

haskell - 转换具有两个以上 Action 的 "do"表示法以使用绑定(bind)函数

asp.net - 确定 ASP.NET 请求的优先级

haskell - `-O99` 标志有什么作用?

python - 将一个巨大的数字乘以 random() (Python)

haskell - Haskell 中 print 和 putStrLn 的区别

haskell - 如何升级 Haskell 平台

haskell - 如何在Morte上创建 `enumFromTo`函数?

haskell - 为什么 HXT 的 xpath 搜索器不返回简单查询的结果?

haskell - 异常类型错误

swift - 如何在非标准库 Swift 框架中为容器启用高级 SIL 优化?