GHC评估策略

时间:2014-03-07 04:35:44

标签: haskell ghc lazy-evaluation

我对使用GHC 7.6.3编译后如何执行以下代码感到困惑。

import qualified Data.Map as M

main = do let m1 = M.fromList $ zip [1..10000000] [1..]
          putStrLn $ "Value = " ++ (show $ M.map (+2) m1 M.! 555)

使用ghc --make -O3编译,它会得到以下结果:

$ /usr/bin/time ./MapLazy
Value = 557
29.88user 2.16system 0:32.12elapsed 99%CPU (0avgtext+0avgdata 2140348maxresident)k
0inputs+0outputs (0major+535227minor)pagefaults 0swaps

然而,如果我将其更改为show $ m1 M.! 555,我会降低内存使用量,但几乎需要相同的时间:

$ /usr/bin/time ./MapLazy
555
23.82user 1.17system 0:25.06elapsed 99%CPU (0avgtext+0avgdata 1192100maxresident)k
0inputs+0outputs (0major+298165minor)pagefaults 0swaps

这里到底发生了什么?整个Map是否因为我读出一个值而实例化?我可以以某种方式阻止它吗?我的意思是,它是一个二叉搜索树,所以我只期待我在新地图上查找的一条路径实际进行评估。

1 个答案:

答案 0 :(得分:3)

我想我明白了。让我们看一下Data.Map.map的来源。

map :: (a -> b) -> Map k a -> Map k b
map f m
  = mapWithKey (\_ x -> f x) m

mapWithKey :: (k -> a -> b) -> Map k a -> Map k b
mapWithKey _ Tip = Tip
mapWithKey f (Bin sx kx x l r) 
  = Bin sx kx (f kx x) (mapWithKey f l) (mapWithKey f r)

现在,mapWithKey似乎构建了树的顶级构造函数,并且懒惰地递归了两个分支......但是:

data Map k a  = Tip 
              | Bin {-# UNPACK #-} !Size !k a !(Map k a) !(Map k a) 

上面我们看到子树是严格的字段!因此,将强制对mapWithKey的递归调用,导致完整的树被严格更新而不是懒惰。