Есть ли утечка места в этой реализации LPath на Haskell?

Ради интереса я пытаюсь написать реализацию наивного алгоритма самого длинного пути (для определения длины самого длинного ациклического пути в циклическом графе). Я начал с прямого переноса императивного алгоритма, который работал и работал достаточно хорошо.

data Route = Route {dest:: !Int32, cost:: !Int32}

type Node = [Route]

lPathImperative :: V.Vector Node -> Int32 -> UMV.IOVector Bool -> IO (Int32)
lPathImperative !nodes !nodeID !visited = do
  UMV.write visited (fromIntegral nodeID) True
  max <- newIORef 0
  Prelude.mapM_  (\ Route{dest, cost} -> do
         isVisited <- UMV.read visited (fromIntegral dest)
         case isVisited of
           True -> return ()
           False -> do
               dist <- fmap (+ cost) $ lPathImperative nodes dest visited
               maxVal <- readIORef max
               if dist > maxVal then writeIORef max dist else return ())
     (nodes V.! (fromIntegral nodeID))
  UMV.write visited (fromIntegral nodeID) False
  readIORef max

Где visited - это неупакованный изменяемый вектор логических значений, представляющий, был ли в данный момент посещен каждый узел в графе, все ли инициализированы значением false, а узлы - это вектор узлов.

Затем я попытался сделать его более функциональным, указав max в качестве значения, которое передается в свертке, а не как IORef, как показано ниже:

lPathFun :: V.Vector Node -> Int32 -> UMV.IOVector Bool -> IO (Int32)
lPathFun !nodes !nodeID !visited = do
  UMV.write visited (fromIntegral nodeID) True
  let max = CM.foldM acc (0::Int32) (nodes V.! (fromIntegral nodeID))
  UMV.write visited (fromIntegral nodeID) False
  max
    where
      acc :: Int32 -> Route -> IO (Int32)
      acc maxDist Route{dest,cost}  = do
          isVisited <- UMV.read visited (fromIntegral dest)
          case isVisited of
            True -> return maxDist
            False -> do
              dist <- fmap (+ cost) $ lPathFun nodes dest visited
              return $ if dist > maxDist then dist else maxDist

Однако эта версия не может быть завершена, она работает несколько минут (другой - секунды для того же ввода), прежде чем умереть с out of memory (requested 1048576 bytes). Я был бы признателен, если бы кто-нибудь мог просмотреть мой код для lPathFun и увидеть, что я делаю не так. Я пытался сделать все в нем строгим, но это не помогло, а также попытался сделать все ленивым, без изменений. Я даже попытался изменить type node на V.Vector route и использовать вместо него строго foldM', но безрезультатно.

Я подозреваю, что проблема в утечке места. Это потому, что я попытался перевести lPathFun в OCaml, и он работал нормально (тот факт, что версия OCaml использует ручную рекурсию, не должен иметь значения: моя функциональная версия Haskell изначально также использовала ручную рекурсию, но имела те же проблемы, что и при использовании foldM) :

type route = {dest: int; cost: int}
type node = route array

let rec lPathFun (nodes: node array) nodeID visited =
  visited.(nodeID) <- true;
  let rec loop i maxDist =
    if i < 0 then maxDist
    else
      let neighbour = nodes.(nodeID).(i) in
      if (not visited.(neighbour.dest))
      then
        let dist = neighbour.cost + lPathFun nodes neighbour.dest visited in
        let newMax = if dist > maxDist then dist else maxDist in
        loop (i-1) newMax
      else
        loop (i-1) maxDist in
  let (max: int) = loop (Array.length nodes.(nodeID) - 1) 0 in
  visited.(nodeID) <- false;
  max;;

Я использую версию GHC 7.8.3.


person LogicChains    schedule 30.11.2014    source источник
comment
Вы можете найти эту статью полезной для устранение утечек пространства.   -  person Rufflewind    schedule 30.11.2014
comment
Можете ли вы дать нам полную работающую программу, которую мы можем запустить для диагностики? Основная функция, передающая некоторый пример ввода в lPathFun, должна быть достаточной.   -  person Tom Ellis    schedule 30.11.2014
comment
Я подозреваю, что с вашим алгоритмом есть более серьезная проблема. В настоящее время вы избегаете выполнения вычисления max до тех пор, пока после не запишете False в позицию nodeID. Разве вы не хотите max <- CM.foldM ...; ...; return max вместо let max = CM.foldM ...; ...; max?   -  person Tom Ellis    schedule 30.11.2014
comment
user5402 опередил меня!   -  person Tom Ellis    schedule 30.11.2014


Ответы (1)


let max = ... здесь выглядит подозрительно:

lPathFun !nodes !nodeID !visited = do
  UMV.write visited (fromIntegral nodeID) True
  let max = CM.foldM acc (0::Int32) (nodes V.! (fromIntegral nodeID))
  UMV.write visited (fromIntegral nodeID) False
  max

Ваш код эквивалентен:

  UMV.write ... True
  UMV.write ... False
  CM.foldM acc ...

но я уверен, что ты хочешь:

  UMV.write visited ... True
  max <- CM.foldM ...
  UMV.write visited ... False
  return max
person ErikR    schedule 30.11.2014
comment
Спасибо! Похоже, я не понимаю нотацию do так хорошо, как думал. - person LogicChains; 30.11.2014