我正在尝试优化程序的执行速度,我遇到了一些有趣的结果,我希望有人可以回答。似乎在我的一个列表推导中做了一些小改动,大大改变了执行速度,但我不知道为什么。
这是我现在的程序。
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
的转储。我真的不知道我在看什么,所以如果有人能够解释,那将是一个很大的帮助,谢谢。
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秒以上。
答案 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似乎做了正确的事情来保持n
当ts
需要严格时,非严格,因为评估n
首先,可能在不同的地方失败可能是一个错误。
接下来,如何强制!ts
对n
产生影响?请注意ts
如果n
,gs
为current
,则m
不在底部,
或n
已知不在底(这些是表达式的所有元素,但M.!
除外)且已经过评估(我认为!
和ts
可能
如果不首先评估他们的论点,就永远不会失败所以我们需要强加条件“n
是底层暗示
n
已经过了底层并且已经过评估“,因此ghc知道首先评估current
是安全的。
我的解决方案:将爆炸模式添加到gs
,m
和current
。用我的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是什么。