为什么putStrLn每次迭代都会变慢?

时间:2020-11-12 18:05:47

标签: haskell conways-game-of-life

我制作了一个小型的“人生游戏”程序,它本身可以代代相传。问题在于,每次迭代时,putStrLn函数的运行速度都会大大降低,我无法弄清原因。这是代码:

import Control.Concurrent

data CellState = Dead | Alive

data Position = Position Integer Integer

type Generation = Position -> CellState

is_alive :: CellState -> Bool
is_alive Alive = True
is_alive Dead = False

neighbors :: Position -> [Position]
neighbors (Position x y) =
  [(Position (x-1) (y-1)), (Position x (y-1)),  (Position (x+1) (y-1)), (Position (x+1) y),
  (Position (x+1) (y+1)), (Position x (y+1)), (Position (x-1) (y+1)), (Position (x-1) y)]

alive_neighbors :: Generation -> Position -> Int
alive_neighbors generation position = length (filter is_alive (map generation (neighbors position)))

evolution :: Generation -> Generation
evolution generation position =
  case (alive_neighbors generation position) of
  2 -> if (is_alive (generation position)) then Alive else Dead
  3 -> Alive
  _ -> Dead

visualize_generation generation = map (visualize_line generation) [1..10]

visualize_line :: Generation -> Integer -> String
visualize_line generation y = concat (map (visualize_cell generation y) [1..10])

visualize_cell generation y x =
  case (generation (Position x y)) of
  Alive -> ['0']
  Dead -> ['.']

{-
bar (Position 1 2) = Alive
bar (Position 2 3) = Alive
bar (Position 3 3) = Alive
bar (Position 3 2) = Alive
bar (Position 3 1) = Alive
bar (Position x y) = Dead
-}

bar (Position 1 3) = Alive
bar (Position 2 3) = Alive
bar (Position 3 3) = Alive
bar (Position x y) = Dead

life :: Generation -> IO ()
life bar_ = do cls
               mapM_ putStrLn (visualize_generation bar_)
               threadDelay 1000000 
               life (evolution bar_)

cls = putStr "\ESC[2J"

最初,我希望每个新一代都出于某种原因也计算所有前几代,但事实并非如此。如果是这种情况,我希望Evolution函数的计算时间会增加,而不是putStrLn函数的打印速度会变慢。关于什么会使每一代的putStrLn函数减速这么多的任何想法?

1 个答案:

答案 0 :(得分:5)

(免责声明:这只是一个猜测,我可能是错的。我没有进行实验来确认这一点。)

这是使用函数表示网格时要付出的代价

type Generation = Position -> CellState

这是一种很好的表示状态的方式,但是从长远来看,它并不是很有效。当您的算法运行时,它会创建很多闭包:

generation0 = \position -> ....
generation1 = \position -> .... use generation0
generation2 = \position -> .... use generation1
generation3 = \position -> .... use generation2
...

即使只需要最后一代,所有上一代的数据仍保留在内存中,因为上一代(间接)使用了这些数据。因此,您永远不会释放内存,这已经很糟糕了。

更糟糕的是,每次使用世代N时,它将多次调用世代N-1(8),而这又将多次调用世代N-2(8),依此类推直到生成0为止。这会导致指数爆炸。

要解决此问题,您需要将数据表示形式更改为更有效的形式。我认为某些类似矩阵的类型可以工作。