[英]Haskell explicit recursion vs `iterate`
While writing a function using iterate
in Haskell, I found that an equivalent version with explicit recursion seemed noticeably faster - even though I believed that explicit recursion ought to be frowned upon in Haskell. 在Haskell中使用
iterate
编写函数时,我发现具有显式递归的等效版本似乎明显更快 - 尽管我认为在Haskell中应该不赞成显式递归。
Similarly, I expected GHC to be able to inline/optimise list combinators appropriately so that the resulting machine code is at least similarly performing to the explicit recursion. 类似地,我期望GHC能够适当地内联/优化列表组合器,以便得到的机器代码至少类似地执行显式递归。
Here's a (different) example, which also displays the slowdown I observed. 这是一个(不同的)示例,它还显示了我观察到的减速情况。
steps mn
and its variant steps'
compute the number of Collatz steps n
takes to reach 1, giving up after m
attempts. steps mn
及其变体steps'
计算达到1所需的Collatz步n
,在m
尝试后放弃。
steps
uses explicit recursion while steps'
uses list functions. steps
使用显式递归,而steps'
使用列表函数。
import Data.List (elemIndex)
import Control.Exception (evaluate)
import Control.DeepSeq (rnf)
collatz :: Int -> Int
collatz n
| even n = n `quot` 2
| otherwise = 3 * n + 1
steps :: Int -> Int -> Maybe Int
steps m = go 0
where go k n
| n == 1 = Just k
| k == m = Nothing
| otherwise = go (k+1) (collatz n)
steps' :: Int -> Int -> Maybe Int
steps' m = elemIndex 1 . take m . iterate collatz
main :: IO ()
main = evaluate $ rnf $ map (steps 800) $ [1..10^7]
I tested these by evaluating for all values up to 10^7
, each giving up after 800
steps. 我通过评估高达
10^7
所有值来测试这些值,每个值在800
步后放弃。 On my machine (compiled with ghc -O2
), explicit recursion took just under 4 seconds ( 3.899s
) but list combinators took about 5 times longer ( 19.922s
). 在我的机器上(使用
ghc -O2
编译),显式递归只花了不到4秒( 3.899s
)但列表组合器花了大约5倍( 19.922s
)。
Why is explicit recursion so much better in this case, and is there a way of writing this without explicit recursion while preserving performance? 在这种情况下,为什么显式递归会更好,并且有没有一种方法可以在保持性能的同时编写没有显式递归的方法?
Updated: I submitted Trac 15426 for this bug. 更新:我为此错误提交了Trac 15426 。
The problem disappears if you copy the definitions of elemIndex
and findIndex
into your module: 如果将
elemIndex
和findIndex
的定义复制到模块中,问题就会消失:
import Control.Exception (evaluate)
import Control.DeepSeq (rnf)
import Data.Maybe (listToMaybe)
import Data.List (findIndices)
elemIndex :: Eq a => a -> [a] -> Maybe Int
elemIndex x = findIndex (x==)
findIndex :: (a -> Bool) -> [a] -> Maybe Int
findIndex p = listToMaybe . findIndices p
collatz :: Int -> Int
collatz n
| even n = n `quot` 2
| otherwise = 3 * n + 1
steps' :: Int -> Int -> Maybe Int
steps' m = elemIndex 1 . take m . iterate collatz
main :: IO ()
main = evaluate $ rnf $ map (steps' 800) $ [1..10^7]
The problem seems to be that these must be inlinable for GHC to get the fusion right. 问题似乎是GHC必须能够使这些融合得当。 Unfortunately, neither of them is marked inlinable in
Data.OldList
. 不幸的是,它们都没有在
Data.OldList
标记为Data.OldList
。
The change to allow findIndex
to participate in fusion is relatively recent (see Trac 14387 ) where listToMaybe
was reimplemented as a foldr
. 允许
findIndex
参与融合的更改是相对较新的(参见Trac listToMaybe
),其中listToMaybe
重新实现为foldr
。 So, it probably hasn't seen a lot of testing yet. 所以,它可能还没有看到很多测试。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.