Optimization GHC真的永远不会内联地图、scanl、foldr等吗。?
我注意到上面说“对于自递归函数,循环断路器只能是函数本身,因此内联pragma总是被忽略。” 这不是说像Optimization GHC真的永远不会内联地图、scanl、foldr等吗。?,optimization,haskell,inline,ghc,Optimization,Haskell,Inline,Ghc,我注意到上面说“对于自递归函数,循环断路器只能是函数本身,因此内联pragma总是被忽略。” 这不是说像map,zip,scan*,fold*,sum等常见递归函数构造的每个应用程序都不能内联吗 当您使用这些函数时,您可以随时重写它们,添加适当的严格性标记,或者使用建议的“流融合”之类的奇特技术 然而,所有这些不都极大地限制了我们编写既快速又优雅的代码的能力吗 对于自递归函数,循环断路器只能是函数本身,因此始终忽略内联杂注 如果某个东西是递归的,要内联它,您必须知道它在编译时执行了多少次。考虑到
map
,zip
,scan*
,fold*
,sum
等常见递归函数构造的每个应用程序都不能内联吗
当您使用这些函数时,您可以随时重写它们,添加适当的严格性标记,或者使用建议的“流融合”之类的奇特技术
然而,所有这些不都极大地限制了我们编写既快速又优雅的代码的能力吗
对于自递归函数,循环断路器只能是函数本身,因此始终忽略内联杂注
如果某个东西是递归的,要内联它,您必须知道它在编译时执行了多少次。考虑到它将是一个可变长度的输入,这是不可能的
然而,所有这些不都极大地限制了我们编写既快速又优雅的代码的能力吗
尽管有一些技术可以使递归调用比正常情况下快得多。例如,尾部调用优化(tail call optimization)简而言之,不像您想象的那样频繁。原因是在实现这些库时采用了流融合等“奇特技术”,图书馆用户不必担心它们 考虑
Data.List.map
。基本包将map
定义为
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
这个映射是自递归的,所以GHC不会内联它
但是,base
还定义了以下重写规则:
{-# RULES
"map" [~1] forall f xs. map f xs = build (\c n -> foldr (mapFB c f) n xs)
"mapList" [1] forall f. foldr (mapFB (:) f) [] = map f
"mapFB" forall c f g. mapFB (mapFB c f) g = mapFB c (f.g)
#-}
这将取代使用map
via,然后,如果无法融合该功能,则将其替换为原始map
。因为融合是自动发生的,所以它不依赖于用户是否意识到它
为了证明这一切都有效,您可以检查GHC为特定输入生成的内容。对于此功能:
proc1 = sum . take 10 . map (+1) . map (*2)
eval1 = proc1 [1..5]
eval2 = proc1 [1..]
当使用-O2编译时,GHC将所有proc1
融合为一个递归形式(如-ddump siml
的核心输出所示)
当然,这些技术所能达到的效果是有限的。例如,简单的平均函数,mean xs=sum xs/length xs
可以轻松地手动转换为单个折叠,但是目前还没有已知的方法在标准函数和融合框架之间自动转换。因此,在这种情况下,用户确实需要意识到编译器生成的代码的局限性
因此,在许多情况下,编译器足够先进,可以创建快速而优雅的代码。了解他们何时会这样做,以及编译器何时可能崩溃,是学习如何编写高效Haskell代码的一个重要部分。事实上,GHC目前无法内联递归函数。然而:
- GHC仍将专门研究递归函数。例如,给定
GHC将发现fac :: (Eq a, Num a) => a -> a fac 0 = 1 fac n = n * fac (n-1) f :: Int -> Int f x = 1 + fac x
用于类型fac
,并为该类型生成一个专门版本的Int->Int
,该版本使用快速整数算法 这种专业化在模块内自动发生(例如,如果在同一模块中定义了fac
和fac
)。对于跨模块专业化(例如,如果在不同模块中定义了f
和f
),则使用以下标记待专业化功能:fac
- 有一些手动转换使函数不可重复。最低功耗技术是,它适用于参数在递归调用时不改变的递归函数(例如许多高阶函数,如
,map
,filter
)。这种转变fold*
进入 这样一个电话,比如map f [] = [] map f (x:xs) = f x : map f xs
将有g :: [Int] -> [Int] g xs = map (2*) xs
内联并成为map
此转换已应用于前奏曲函数,如g [] = [] g (x:xs) = 2*x : g xs
和foldr
foldl
- 融合技术还可以使许多函数成为非递归函数,并且比静态参数转换更强大。编入序曲的列表的主要方法是。基本方法是尽可能多地编写使用
和/或foldr
的非递归函数;然后在build
中捕获所有递归,并且有专门的规则来处理foldr
利用这种融合原则上很容易:避免手动递归,更喜欢库函数,如foldr
、foldr
、map
,以及中的任何函数。特别是,以这种风格编写代码会产生“同时快速和优雅”的代码过滤器
- 现代图书馆,如图书馆和后台使用。唐·斯图尔特(Don Stewart)写了两篇博客文章(,)在现在已经过时的图书馆中证明了这一点,但同样的原则也适用于文本和向量 与快捷方式融合一样,利用文本和向量中的流融合原则上很容易:避免手动递归,更喜欢标记为“subject to fusion”的库函数
- 目前正在进行改进GHC的工作,以支持递归函数的内联。这属于的总标题下,最近的工作似乎是由和领导的
让{a=map f(cycle foo);b=scan g0a;c=zipwhith hab;d=takeWhile(>=0)b}插入(fst$last$zip cd,最大值d)
。理想情况下,我们应该重写它,使其成为一个简单的循环或尾部递归,只使用几个寄存器,一个用于a
,b
,c
,d
,当前最大值fromd
,以及foo
的索引,bu
g :: [Int] -> [Int]
g xs = map (2*) xs
g [] = []
g (x:xs) = 2*x : g xs