Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/loops/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Performance Haskell显式递归与迭代`_Performance_Loops_Haskell_Ghc_Compiler Optimization - Fatal编程技术网

Performance Haskell显式递归与迭代`

Performance Haskell显式递归与迭代`,performance,loops,haskell,ghc,compiler-optimization,Performance,Loops,Haskell,Ghc,Compiler Optimization,在Haskell中使用iterate编写函数时,我发现使用显式递归的等效版本似乎要快得多——尽管我认为在Haskell中显式递归应该受到反对 类似地,我希望GHC能够适当地内联/优化列表组合器,以便生成的机器代码至少与显式递归的执行类似 这里有一个(不同的)例子,它也显示了我观察到的减速 steps mn及其变体steps'计算Collatz步数n达到1所需的步数,尝试m后放弃 steps使用显式递归,而steps'使用列表函数 import Data.List (elemIndex) impo

在Haskell中使用
iterate
编写函数时,我发现使用显式递归的等效版本似乎要快得多——尽管我认为在Haskell中显式递归应该受到反对

类似地,我希望GHC能够适当地内联/优化列表组合器,以便生成的机器代码至少与显式递归的执行类似

这里有一个(不同的)例子,它也显示了我观察到的减速

steps mn
及其变体
steps'
计算Collatz步数
n
达到1所需的步数,尝试
m
后放弃

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]
我通过对
10^7
之前的所有值进行评估来测试这些值,每个值在
800
步骤后都放弃。在我的机器上(用
ghc-O2
编译),显式递归只花了不到4秒的时间(
3.899s
),但列表组合器花了大约5倍的时间(
19.922s

为什么显式递归在这种情况下要好得多,有没有一种方法可以在不使用显式递归的情况下编写它,同时保持性能?

更新:我提交了这个bug

如果将
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]
问题似乎是,为了使GHC获得正确的聚变,这些必须是内联的。不幸的是,它们在
Data.OldList
中都没有标记为Inlineable


允许
findIndex
参与融合的更改相对较新(请参阅),其中
listToMaybe
被重新实现为
foldr
。因此,它可能还没有看到很多测试。

您是否考虑过检查您对GHC的预期是否正确?如果不是,您的
迭代版将生成、匹配和收集数十亿个元素,这将大大有助于解释差异。@最终,我相当肯定我对GHC的期望过高,但我相当肯定Haskell的懒惰可以防止在
迭代
中生成数十亿个不必要的元素,然后再使用它们。可以公平地假设每个
迭代
在生成的元素不超过800个之后就会停止,但800*10^7仍然是数十亿。我很难理解这是如何证明差异的,由于两个版本都必须进行相同数量的计算,因此这两种实现的计算结果分别是25秒和53秒
/foo+RTS-s
显示快速版本在堆上分配了
1200041152字节
vs
151781624928字节
,这确实表明正在生成和搜索列表。算术计算的数量通常不是工作量的一个很好的衡量标准,因为与内存访问或分支预测失误相比,算术计算的数量是多么便宜,或者CS分析经常认为算术计算是免费的。很少看到这类问题揭示语言本身的缺陷。我觉得我在尼斯湖水下瞥见了什么东西!哇!我原以为我对
iterate
融合的理解有缺陷,而不是遗漏了一个内联词。@AdamSmith但这不是语言的缺陷,只是GHC,一个特殊的Haskell编译器的缺陷。@Cactus挑剔:)但你是对的,GHC/=Haskell,即使很容易理解为什么有人会这么认为。