有啥能阻止优化尾递归?

Posted

技术标签:

【中文标题】有啥能阻止优化尾递归?【英文标题】:Anything prevents optimizing tail-recursion?有什么能阻止优化尾递归? 【发布时间】:2013-06-27 14:39:24 【问题描述】:

我正在使用动态编程在 Haskell 中解决一个背包问题。我的第一次尝试是构建一个二维表。但是当输入很大时(例如 100 * 3190802 的表),内存很容易被炸毁。

知道任何给定的行 i 只依赖于行 (i - 1),我改为编写一个函数以希望利用尾递归:

import Data.Vector (Vector, (!))
import qualified Data.Vector as V

-- n items, k capacity, vs values, ws weights
ans:: Int -> Int -> Vector Int -> Vector Int -> Int
ans n k vs ws =
    let row = initRow k vs ws
    in  row ! k

initRow :: Int -> Vector Int -> Vector Int -> Vector Int
initRow k vs ws = itbl 1 $ V.replicate (k + 1) 0
    where n = V.length vs
          itbl i row
             | i > n = row
             | otherwise = itbl (i + 1) $ V.generate (k + 1) gen
             where gen w =
                       let w_i = ws ! (i - 1)
                           no_i = row ! w
                           ok_i = row ! (w - w_i) + (vs ! (i - 1))
                       in
                           if w < w_i then no_i
                           else max no_i ok_i

如代码所示,itbl 以递归方式调用自身,不会对其返回值进行进一步计算。但是,我仍然看到top 中的内存不断增长:

 VIRT   PID USER      PR  NI  RES  SHR S  %CPU %MEM    TIME+  COMMAND
1214m  9878 root      20   0 424m 1028 S  40.8 85.6   0:16.80 ghc

代码中是否有任何内容阻止编译器为尾递归生成优化代码?

code data

--

【问题讨论】:

出于好奇,尝试从itbl (i + 1) $ V.generate (k + 1) gen 调用中删除$ 运算符,使其看起来像这样:itbl (i + 1) (V.generate (k + 1) gen)。我的猜测是 $ 使调用不是尾递归的。 谢谢。但删除$ 似乎没有帮助。内存使用量仍在不断增长。 我猜这是懒惰的问题。在惰性语言中,尾递归是不够的(也不是总是可取的)。 尝试用$!替换$ 感谢大家指出正确的方法。是的,这是关于严格的。 【参考方案1】:

这是一个严格性问题。在

中调用generate
             | otherwise = itbl (i + 1) $ V.generate (k + 1) gen

实际上并不强制向量进入内存。

您可以import Control.DeepSeq 和替换$ 为非常严格的应用程序$!!

             | otherwise = itbl (i + 1) $!! V.generate (k + 1) gen

或者您可以使用未装箱的向量(可能更快),通过将导入语句更改为

import Data.Vector.Unboxed (Vector, (!))
import qualified Data.Vector.Unboxed as V

(并将其他所有内容保留在原始程序中)。

【讨论】:

以上是关于有啥能阻止优化尾递归?的主要内容,如果未能解决你的问题,请参考以下文章

尾递归优化

如何看待以及理解Python的这种尾递归优化

如何解决栈溢出

Python开启尾递归优化!

Oz 中的尾递归优化

kotlin学习笔记之尾递归优化(tailrec)