[英]Is performance of partial or curried functions well defined in Haskell?
在以下代码中:
ismaxl :: (Ord a) => [a] -> a -> Bool
ismaxl l x = x == maxel
where maxel = maximum l
main = do
let mylist = [1, 2, 3, 5]
let ismax = ismaxl mylist
--Is each call O(1)? Does each call remember maxel?
let c1 = ismax 1
let c2 = ismax 2
let c3 = ismax 3
let c5 = ismax 5
putStrLn (show [c1, c2, c3, c5])
部分函数是否为max,计算maxel? 特别是,有人可以指出关于Haskell中部分函数的复杂性的规则吗? 在上面的例子中,编译器必须只调用一次最大值吗? 换句话说,部分函数是否保留了内部where子句的先前调用的引用?
我有一些CPU限制的代码不能令人满意,我正在寻找可能的错误,我的理由是复杂性。
作为您可以从分析Haskell代码中学到的东西的演示,这里是对代码进行一些小修改的结果。 首先,我用[0..10000000]
替换了mylist
以确保计算最大值需要一段时间。
在运行该版本之后,以下是分析输出中的一些行:
COST CENTRE MODULE %time %alloc
ismaxl Main 55.8 0.0
main Main 44.2 100.0
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 1 0 0.0 0.0 100.0 100.0
CAF:main_c5 Main 225 1 0.0 0.0 15.6 0.0
main Main 249 0 0.0 0.0 15.6 0.0
ismaxl Main 250 1 15.6 0.0 15.6 0.0
CAF:main_c3 Main 224 1 0.0 0.0 15.6 0.0
main Main 246 0 0.0 0.0 15.6 0.0
ismaxl Main 247 1 15.6 0.0 15.6 0.0
CAF:main_c2 Main 223 1 0.0 0.0 14.3 0.0
main Main 243 0 0.0 0.0 14.3 0.0
ismaxl Main 244 1 14.3 0.0 14.3 0.0
CAF:main_c1 Main 222 1 0.0 0.0 10.4 0.0
main Main 239 0 0.0 0.0 10.4 0.0
ismaxl Main 240 1 10.4 0.0 10.4 0.0
CAF:main8 Main 221 1 0.0 0.0 44.2 100.0
main Main 241 0 44.2 100.0 44.2 100.0
这显然是重新计算最大值。
现在,用这个替换ismaxl
:
ismaxl :: (Ord a) => [a] -> a -> Bool
ismaxl l = let maxel = maximum l in (== maxel)
......再次分析:
COST CENTRE MODULE %time %alloc
main Main 60.5 100.0
ismaxl Main 39.5 0.0
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 1 0 0.0 0.0 100.0 100.0
CAF:main_c5 Main 227 1 0.0 0.0 0.0 0.0
main Main 252 0 0.0 0.0 0.0 0.0
ismaxl Main 253 1 0.0 0.0 0.0 0.0
CAF:main_c3 Main 226 1 0.0 0.0 0.0 0.0
main Main 249 0 0.0 0.0 0.0 0.0
ismaxl Main 250 1 0.0 0.0 0.0 0.0
CAF:main_c2 Main 225 1 0.0 0.0 0.0 0.0
main Main 246 0 0.0 0.0 0.0 0.0
ismaxl Main 247 1 0.0 0.0 0.0 0.0
CAF:main_c1 Main 224 1 0.0 0.0 0.0 0.0
CAF:main_ismax Main 223 1 0.0 0.0 39.5 0.0
main Main 242 0 0.0 0.0 39.5 0.0
ismaxl Main 243 2 39.5 0.0 39.5 0.0
CAF:main8 Main 222 1 0.0 0.0 60.5 100.0
main Main 244 0 60.5 100.0 60.5 100.0
...这次它大部分时间用于一次调用ismaxl
,其他时间太快甚至没有注意到,所以它必须在这里只计算一次最大值。
这是您的代码的修改版本,可以让您查看是否重用了maxel
:
import Debug.Trace
ismaxl :: (Ord a) => [a] -> a -> Bool
ismaxl l x = x == maxel
where maxel = trace "Hello" $ maximum l
main = do
let mylist = [1, 2, 3, 5]
let ismax = ismaxl mylist
--Is each call O(1)? Does each call remember maxel?
let c1 = ismax 1
let c2 = ismax 2
let c3 = ismax 3
let c5 = ismax 5
putStrLn (show [c1, c2, c3, c5])
你会发现应用程序之间没有“记住” maxel
。
通常,在所有参数都提供给函数之前,您不应期望Haskell开始执行缩减。
另一方面,如果您启用了激进的优化,那么很难预测特定编译器实际会做什么。 但是,您可能不应该依赖编译器的任何部分,当您可以轻松地重写代码以实现您想要的内容时,很难预测。
基于其他好的答案,GHC并不急于根据我的经验进行这种优化。 如果我不能轻易地创造出无点的东西,我经常会在LHS和lambda上使用混合绑定变量来编写:
ismaxl :: (Ord a) => [a] -> a -> Bool
ismaxl l = \x -> x == maxel
where maxel = maximum l
我并不特别喜欢这种风格,但它确实保证maxel
被调用之间共享一个部分应用ismaxl
。
我无法在Haskell报告中找到任何此类要求,事实上GHC似乎默认情况下不会执行此优化。
我把你的main
功能改成了
main = do
let mylist = [1..99999]
let ismax = ismaxl mylist
let c1 = ismax 1
let c2 = ismax 2
let c3 = ismax 3
let c5 = ismax 5
putStrLn (show [c1, c2, c3, c5])
简单的分析显示(在我的旧Pentium 4上):
$ ghc a.hs
$ time ./a.out
[False,False,False,False]
real 0m0.313s
user 0m0.220s
sys 0m0.044s
但是当我改变c2
, c3
和c5
的定义let c2 = 2 == 99999
等(让c1
原样)时,我得到了
$ ghc a.hs
$ time ./a.out
[False,False,False,False]
real 0m0.113s
user 0m0.060s
sys 0m0.028s
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.