昨天我终于决定开始学习Haskell。我开始扫描教程,但很快就决定实际练习会更有益。因此,我继续移植我的python脚本,据说可以模拟重力进入Haskell。令我惊讶的是它确实有效,生成的值与python的值相匹配。
我意识到实施可能非常糟糕。可怕的性能缺乏并没有给我带来太大的麻烦,但令我困扰的是,当我试图长时间运行模拟时,我的内存不足。这是因为实施本质上存在缺陷还是可以使其发挥作用?
我尝试用三种不同的方法构造主循环:&#34;迭代&#34;,一个递归函数(我读过关于尾递归,但没有设法让它工作)和一个更多实验性递归功能。相关功能名为 模拟 , 测试 和 test2 < / strong>即可。我用选项&#34; -O2&#34;。
编译了程序为什么程序内存不足,我该怎么做才能防止这种情况发生?
代码不是那么相关的部分:
import System.Environment
import Data.List (tails)
import System.CPUTime
import Text.Printf
import Control.Exception
gConst = 6.674e-11
data Vector = Vector2D Double Double | Vector3D Double Double Double deriving (Show)
deltaVector :: Vector -> Vector -> Vector
deltaVector (Vector2D x1 y1) (Vector2D x2 y2) = Vector2D (x2 - x1) (y2 - y1)
deltaVector (Vector3D x1 y1 z1) (Vector3D x2 y2 z2) = Vector3D (x2 - x1) (y2 - y1) (z2 - z1)
data Position = Position Vector deriving (Show)
data Velocity = Velocity Vector deriving (Show)
distance2DSquared (Vector2D deltaX deltaY) = deltaX ** 2 + deltaY ** 2
distance3DSquared (Vector3D deltaX deltaY deltaZ) = (distance2DSquared $ Vector2D deltaX deltaY) + deltaZ ** 2
distance vector = sqrt (distance3DSquared $ vector)
force vector mass1 mass2 = gConst * (mass1 * mass2) / (distance3DSquared vector)
acceleration force mass = force / mass
vectorComponentDivide (Vector2D x y) c = Vector2D (x/c) (y/c)
vectorComponentDivide (Vector3D x y z) c = Vector3D (x/c) (y/c) (z/c)
vectorComponentMultiply (Vector2D x y) c = Vector2D (x*c) (y*c)
vectorComponentMultiply (Vector3D x y z) c = Vector3D (x*c) (y*c) (z*c)
vectorComponentAdd (Vector2D x1 y1) (Vector2D x2 y2) = Vector2D (x1+x2) (y1+y2)
vectorComponentAdd (Vector3D x1 y1 z1) (Vector3D x2 y2 z2) = Vector3D (x1+x2) (y1+y2) (z1+z2)
invertedVector (Vector2D x1 y1) = Vector2D (-x1) (-y1)
invertedVector (Vector3D x1 y1 z1) = Vector3D (-x1) (-y1) (-z1)
normalizedVector :: Vector -> Vector
normalizedVector vector = vectorComponentDivide vector $ distance vector
velocity vel0 mass1 mass2 vector deltaT =
vectorComponentMultiply (vectorComponentAdd vel0 (vectorComponentMultiply (normalizedVector vector) (acceleration (force vector mass1 mass2) mass1))) deltaT
data Actor = Actor String Vector Vector Double deriving (Show)
earth = Actor "Object1" (Vector3D 0 0 0) (Vector3D 0 0 0) 10
moon = Actor "Object2" (Vector3D 10 0 0) (Vector3D 0 0 0) 10
actors = [earth, moon]
combinations :: Int -> [a] -> [[a]]
combinations 0 _ = [ [] ]
combinations n xs = [ y:ys | y:xs' <- tails xs
, ys <- combinations (n-1) xs']
updateVelocity [(Actor name1 position1 velocity1 mass1),(Actor name2 position2 velocity2 mass2)] =
[(Actor name1 position1 a mass1),(Actor name2 position2 b mass2)]
where a = velocity velocity1 mass1 mass2 vector deltaT
b = velocity velocity2 mass2 mass1 (invertedVector vector) deltaT
vector = deltaVector position1 position2
deltaT = 1
updatePosition [(Actor name1 position1 velocity1 mass1),(Actor name2 position2 velocity2 mass2)] =
[Actor name1 (vectorComponentAdd position1 velocity1) velocity1 mass1, Actor name2 (vectorComponentAdd position2 velocity2) velocity2 mass2]
相关部分:
update list = map updatePosition (map updateVelocity list)
simulation state n = do
if n == 0
then do
print state
return ()
else do
let newState = update state
simulation newState $! (n-1)
test list n = iterate update list !! n
test2 list 0 = list
test2 list n = (test2 (update list) (n-1))
time :: IO t -> IO t
time a = do
start <- getCPUTime
v <- a
end <- getCPUTime
let diff = (fromIntegral (end - start)) / (10^12)
printf "Computation time: %0.3f sec\n" (diff :: Double)
return v
main :: IO ()
main = do
let combo = combinations 2 actors
putStrLn "Hello World!"
let n = 1000000
print n
--time $ print (test combo n)
time $ simulation combo n
_ <- getLine
putStrLn "BAI"
答案 0 :(得分:3)
我认为懒惰会损害您的代码:您的代码会构建大量的thunk(未评估的表达式),从而导致OOM。
例如,当您在中间访问结果列表而没有强制之前的列表元素时,iterate
以导致大型thunk而闻名。更确切地说是
iterate f x !! n
很糟糕,因为它会在真正执行任何工作之前构建表达式f (f (f ...(f x)))
。我们想在访问下一个列表元素之前评估每个列表元素。这可以通过自定义!!
函数进行圆顶化:
(!!!) :: [a] -> Int -> a
[] !!! _ = error "!!!: out of range"
(x:_ ) !!! 0 = x
(x:xs) !!! n = seq x (xs !!! pred n)
现在我们可以使用iterate f a !!! n
而不会产生大的暴力。
这有同样的问题:
simulation state n = do
if n == 0
then do
print state
return ()
else do
let newState = update state
simulation newState $! (n-1)
它将构建大型update (update (update ...))
thunks而不评估它们。可能的修复方法可能是
...
(simulation $! newState) $! (n-1)
但是,请记住,在您的情况下,newState
是一个列表(列表!)。在这种情况下,seq
或$!
将仅要求对列表进行评估,直到其第一个单元格构造函数为止 - 足以检查列表是否为空。这个&#34;强迫&#34;可能已经足够或不适合您的目的。
有一个名为deepSeq
的库函数会强制显示完整列表(如果确实需要)(使用Hoogle查找文档)。
总结:懒惰的评估有其好处和缺点。它通常允许更高的效率,例如有时提供恒定的空间列表处理,而无需编写精心设计的功能。它还允许使用无限的列表技巧。但是,它也可能导致不必要的thunk长时间停留,浪费内存。因此,在这些情况下,程序员会有一些负担。特别是当一个习惯于严格的语义时,这些问题一开始可能会让人感到恐惧(我们已经在那里!)。