简单Haskell性能分析
我试图比较在二元搜索树中查找第n个最小数的两种实现的性能。这只是一个玩具学习问题。我天真的测量尝试如下:简单Haskell性能分析,haskell,Haskell,我试图比较在二元搜索树中查找第n个最小数的两种实现的性能。这只是一个玩具学习问题。我天真的测量尝试如下: getNth :: Tree Int -> Int -> Either String Int eval :: [Either b Int] -> Int eval = (foldl (+) 0) . rights main :: IO () main = do let t = foldl treeInsertBalanced EmptyTree [1..1000
getNth :: Tree Int -> Int -> Either String Int
eval :: [Either b Int] -> Int
eval = (foldl (+) 0) . rights
main :: IO ()
main = do
let t = foldl treeInsertBalanced EmptyTree [1..1000000]
first = getNth t 100000
iterations = 100000000000
second = take iterations $ repeat $ getNth t 100
third = take iterations $ repeat $ getNth' t 100
print $ "dummy to cause eval: " ++ (show first)
print ""
time1 <- System.CPUTime.getCPUTime
print $ eval second
time2 <- System.CPUTime.getCPUTime
print $ eval third
time3 <- System.CPUTime.getCPUTime
let secondTime = time2-time1
thirdTime = time3-time2
timeDiff = secondTime - thirdTime
print $ "take version = " ++ (show secondTime)
print $ "opt version = " ++ (show thirdTime)
print $ "diff = " ++ (show timeDiff)
getNth::Tree Int->Int->任一字符串Int
eval::[b Int]->Int
eval=(foldl(+)0)。权利
main::IO()
main=do
设t=foldl树insertbalanced EmptyTree[1..1000000]
第一个=第n个t 100000
迭代次数=10000000000
第二次=迭代次数$repeat$getNth t 100
第三次=迭代次数$repeat$getNth't 100
打印$“dummy以引起评估:”++(先显示)
打印“”
时间1(可在
一些意见:
强制求值的一个好方法是使用Control.deepseq
中的deepseq
repeat
不会重新计算其参数
GHC非常擅长发现相同的表达式,因此有时您必须用相同的参数隐藏函数调用,以使GHC重新评估函数调用
下面是使用deepseq
的示例:
import Control.DeepSeq (deepseq)
import Control.Monad
import Debug.Trace
import System.TimeIt
import System.Environment
theList = [1..8] ++ [undefined] ++ [10] :: [Int]
main1 = do
print $ length theList
print $ deepseq theList (length theList)
第一个print
语句发出10
。第二个抛出异常,因为
deepseq
调用试图计算undefined
元素
<> >查看<代码>重复>代码>不重新评估它的参数,考虑这个例子:
foo = repeat $ trace "(here)" 2
main2 = print $ take 3 foo
运行main2
的结果是:
[(here)
2,2,2]
所发生的事情是,当调用foo
的头以执行repeat
时,对其参数求值。这将调用跟踪
,该跟踪打印(此处)
,并返回2。当需要列表的其余部分时,foo
通过repeat
保存此值
最后,这里演示了GHC在发现具有相同参数的函数调用方面有多么出色
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
theN = 34 -- use 24 if running under ghci
compute1 = fib theN
compute2 k = fib theN
compute3 k = fib (k+theN-k)
fib然后
只是一个函数调用,需要一段时间来计算(大约0.6秒)
一些结论:
- 像
compute1
这样的顶级表达式被存储
- 如果不使用-O2,添加一个被忽略的参数(例如,
compute2
)将欺骗GHC重新计算函数调用
- 对于-O2,可能需要一种更巧妙的方法来伪装函数调用,以使GHC在循环中重新评估它
考虑使用基准测试包,如Criteria。
loop1 n = forM_ [1..n] $ \_ -> print compute1
loop2 n = forM_ [1..n] $ \k -> print (compute2 k)
loop3 n = forM_ [1..n] $ \k -> print (compute3 k)
timeLoop loop = do timeIt $ loop 1
timeIt $ loop 2
timeIt $ loop 3
timeIt $ loop 10
main4 = timeLoop loop1
main5 = timeLoop loop2
main6 = timeLoop loop3
main = do (arg:_) <- getArgs
case arg of
"4" -> main4
"5" -> main5
"6" -> main6
w/o -O2 with -O2
main4 1 secs 0.1 sec
main5 13 secs 0.1 sec
main6 13 secs 1.0 sec