Haskell列表理解速度不一致

时间:2014-07-11 05:05:01

标签: list haskell optimization list-comprehension ghc

我正在尝试优化程序的执行速度,我遇到了一些有趣的结果,我希望有人可以回答。似乎在我的一个列表推导中做了一些小改动,大大改变了执行速度,但我不知道为什么。

这是我现在的程序。

import Data.Ord
import Control.Monad
import Data.Array
import Data.Ix
import qualified Data.Map as M
import qualified Data.Set as S
import Data.List (minimumBy, foldl')


arrayMatrix lists = let rlen = length lists
                        clen = length $ head lists
                        r    = ((1,1), (rlen, clen))
                    in array r . zip (range r) $ concat lists

a_star start goal h m = search S.empty (S.singleton start) 
                        (M.singleton start (m ! start)) 
                        $ M.singleton start (m ! start + h ! start)
    where neighbors (r,c) = filter (inRange $ bounds m) [ (r-1,c), (r,c+1), (r+1,c) , (r,c-1)]
          search closed open gs fs
              | S.null open     = 0
              | current == goal = gs M.! goal
              | otherwise       = let open'   = S.delete current open
                                      closed' = S.insert current closed
                                      neighbs = [(n, ts) | n <- neighbors current, S.notMember n closed
                                                , let ts = gs M.! current + m ! n ]
                                      actionable = filter (\(n,ts) -> S.notMember n open' || ts < (gs M.! n)) neighbs
                                      (op',gs',fs') = foldl' (\(o,ng,nf) (n,ts) -> (S.insert n o, M.insert n ts ng, M.insert n (ts + h ! n) nf)) (open',gs,fs) actionable
                                  in search closed' op' gs' fs'
              where current = minimumBy (comparing (fs M.!)) $ S.toList open

main = do
  matrix <- liftM (arrayMatrix . map (read . ('[':) . (++"]")) . lines) 
            $ readFile "matrix.txt"
  let bds       = bounds matrix
      ulim      = snd bds
      heuristic = let m   = minimum $ elems matrix
                    in listArray bds . map (\(r,c) -> (uncurry (+) ulim)-r-c) $ range bds
  print $ a_star (1,1) ulim heuristic matrix

现在程序在我的计算机上运行~350ms(用GHC 7.8.2 -O2编译)和Project Euler提供的matrix.txt

如果我从

改变邻居
neighbs = [(n, ts) | n <- neighbors current, S.notMember n closed
          , let ts = gs M.! current + m ! n ]

neighbs = [(n, gs M.! current + m ! n) | n <- neighbors current, S.notMember n closed]

执行时间增加到超过1秒 其他一些小的改动,比如将下一行的过滤器移动到列表解析中会产生相同的结果:~1秒 任何人都可以解释为什么会这样吗?

编辑:在早期版本的GHC上似乎不会发生这种情况。我尝试了GHC 7.6.3,并且每个都执行了相同的操作。

我按照cdk的建议,包含了运行ghc -O2 -ddump-simpl -dsuppress-all的转储。我真的不知道我在看什么,所以如果有人能够解释,那将是一个很大的帮助,谢谢。

Link to both dumps

EDIT2(对Priyatham的回应):我认为不是这样的。我改变了

neighbs = [(n, ts) | n <- neighbors current, S.notMember n closed
          , let ts = gs M.! current + m ! n ]
actionable = filter ((n,ts) -> S.notMember n open' || ts < (gs M.! n)) neighbs

neighbs = [(n, gs M.! current + m ! n) | n <- neighbors current, S.notMember n closed ]
actionable = filter ((n,!ts) -> S.notMember n open' || ts < (gs M.! n)) neighbs

使用BangPatterns,但仍然会在一秒钟内运行。事实上,修改neigbs

neighbs = [(n, ts) | n <- neighbors current, S.notMember n closed
          , let ts = gs M.! current + m ! n ]

neighbs = [(n, ts) | n <- neighbors current, S.notMember n closed
          , let !ts = gs M.! current + m ! n ]  -- Added bang before ts

也将运行时间增加到1秒以上。

2 个答案:

答案 0 :(得分:3)

以下是关于let ts =let !ts =发生的事情的猜测。一世 通过查看-ddump-stranal的输出得到它(转储 严格性分析注释)和阅读Demand analyser in GHC

let !ts =let ts =之间的差异在于ts 底部(即undefined),然后n进行评估 因为ts将首先被评估,评估将停止。它 似乎两个程序之间的差异就是那对 整数n在一个版本中是严格的并且是未装箱的,但不在 其他(请参阅-ddump-stranal-ddump-simpl的输出;链接 上面描述了输出)。

!ts!ts如何影响n的严格性?我想如果 在{/ em>评估ts之前,n是最低的,程序必须 它的任何元素(我不确定它是n :: (Int, Int)本身还是 它的元素)。所以ghc似乎做了正确的事情来保持nts需要严格时,非严格,因为评估n 首先,可能在不同的地方失败可能是一个错误。

接下来,如何强制!tsn产生影响?请注意ts 如果ngscurrent,则m不在底部, 或n已知不在底(这些是表达式的所有元素,但M.!除外)且已经过评估(我认为!ts可能 如果不首先评估他们的论点,就永远不会失败所以我们需要强加条件“n是底层暗示 n已经过了底层并且已经过评估“,因此ghc知道首先评估current是安全的。

我的解决方案:将爆炸模式添加到gsmcurrent。用我的ghc 7.8.2,这似乎解决了这个问题。似乎只需强制ts

我不太确定关于移动表达式的原始问题 filter (\x -> x > 5) [x | x <- [1..10]] == [x | x <- [1..10], x > 5] 进入元组,但同样的解决方案似乎有效。

P.S。注意

neighbs

所以在您的列表中actionable[(n, ts) | n <- neighbors current , S.notMember n closed , let ts = gs M.! current + m ! n , S.notMember n open' || ts < (gs M.! n) ] 将过滤谓词带入后会更清晰 列表理解本身如此:

{{1}}

答案 1 :(得分:-2)

这不是一个完整的答案,因为我缺乏有关如何在内部实施let和列表推导的信息。

neighbs中的每个项目都是一个元组,而在WHNF中,总和不会严格评估。这留下了未评估的thunk,这可能会增加运行时间。

我建议在不使用seq的情况下使用let重写第二个定义,如果可能的话,看看运行时是否下降(在这种情况下,这个答案可能是正确的)。

阅读this以了解WHNF是什么。