程序生成Haskell中的大量值列表 - 最惯用的方法?内存管理?

时间:2013-03-07 09:03:25

标签: haskell idiomatic

我有一个函数,它接受一系列随机数/浮点数,并使用它们来生成一个值/结构(即,获取一个球的随机速度和位置,并输出它的坐标)会落地)。而且我需要连续产生几千个。

我实现一切的方式是每次计算都采用stdGen,用它来生成几个数字,并传递一个新的stdGen以允许它被链接到另一个。

要为10000个项目执行此操作,我从generate_item n创建一个列表,它基本上输出一个(value,gen)元组(值是我正在尝试计算的值),其中值gen的{​​{1}}是从generate_item n-1获取值所涉及的计算中递归输出的stdGen

然而,这个程序似乎在大约一千个结果左右的时候爬行变得不切实际。而且似乎绝对不具备可扩展性。这可能与我将所有generate_item结果存储在内存中的事实有关吗?

或者是否有一种更加自觉的方式在Haskell中使用Monads或者上面描述的内容来解决这个问题?

请注意,即使在像ruby和python这样的高级脚本语言中,从随机值生成算法的代码也会在几秒内生成10k;这些计算几乎不是密集的。

代码

-- helper functions that take in StdGen and return (Result,new StdGen)
plum_radius :: StdGen -> (Float,StdGen)
unitpoint   :: Float -> StdGen -> ((Float,Float,Float),StdGen)
plum_speed  :: Float -> StdGen -> (Float,StdGen)

-- The overall calculation of the value
plum_point  :: StdGen -> (((Float,Float,Float),(Float,Float,Float)),StdGen)
plum_point gen  = (((px,py,pz),(vx,vy,vz)),gen_out)
  where
    (r, gen2)         = plum_radius gen
    ((px,py,pz),gen3) = unitpoint r gen2
    (s, gen4)         = plum_speed r gen3
    ((vx,vy,vz),gen5) = unitpoint s gen4
    gen_out           = gen5

-- Turning it into some kind of list
plum_data_list  :: StdGen -> Int -> (((Float,Float,Float),(Float,Float,Float)),StdGen)
plum_data_list seed_gen 0  = plum_point seed_gen
plum_data_list seed_gen i  = plum_point gen2
  where
    (_,gen2)  = plum_data_list seed_gen (i-1)

-- Getting 100 results
main = do
  gen <- getStdGen
  let data_list = map (plum_data_list gen) [1..100]
  putStrLn List.intercalate " " (map show data_list)

3 个答案:

答案 0 :(得分:6)

考虑使用mersenne-twister和vector-random软件包,该软件包经过专门优化,可生成大量高质量的随机数据。

列表不适合分配大量数据 - 更好地使用打包表示 - 除非您正在流式传输。

答案 1 :(得分:5)

首先,您正在描述的模式 - 采用StdGen然后返回一个带有值的元组,另一个StdGen将被链接到下一个计算中 - State monad编码的模式。重构代码以使用它可能是开始熟悉monadic模式的好方法。

至于您的性能问题,StdGen的速度非常慢。我没有做过很多这方面的事情,但我听说mersenne twister更快。

但是,您可能还想发布您的代码,因为在您生成大型列表的情况下,根据您的操作方式,懒惰可能对您有利或不利。但是如果不了解你在做什么就很难给出具体的建议。一个经验法则,以防你来自另一个函数语言,如Lisp - 生成列表(或其他惰性数据结构 - 例如树,但不是Int),避免< / strong>尾递归。它更快的直觉不会转移到懒惰的语言。例如。使用(没有我在练习中实际使用的monadic风格的书写)

randoms :: Int -> StdGen -> (StdGen, [Int])
randoms 0 g = (g, [])
randoms n g = let (g',  x)  = next g
                  (g'', xs) = randoms (n-1) g'
              in (g'', x : xs)

这将允许结果列表“流式传输”,因此您可以在生成后续部分之前访问它的早期部分。 (在这种情况下,它有点微妙,因为访问生成的StdGen将必须生成整个列表,所以你必须小心避免这样做,直到你消耗了列表 - 我希望有一个支持良好split操作的快速随机生成器,然后你可以完全返回一个生成器。

哦,以防你在使用monad事物时遇到问题,这是上面用状态monad编写的函数:

randomsM :: Int -> State StdGen [Int]
randomsM 0 = return []
randomsM n = do
    x <- state next
    xs <- randomsM (n-1)
    return (x : xs)

看信件?

答案 2 :(得分:4)

其他海报有好点,StdGen表现不佳,你应该尝试使用State而不是手动传递发生器。但我认为最大的问题是你的plum_data_list功能。

它似乎是某种查找,但由于它是在没有任何记忆的情况下递归实现的,因此您所做的调用必须递归到基本情况。也就是说,plum_data_list seed_gen 100需要来自plum_data_list seed_gen 99的随机生成器,依此类推,直到plum_data_list seed_gen 0。当您尝试生成这些值的列表时,这将为您提供二次性能。

可能更惯用的方法是让plum_data_list seed_gen生成无限的点列表,如下所示:

plum_data_list :: StdGen -> [((Float,Float,Float),(Float,Float,Float))]
plum_data_list seed_gen = first_point : plum_data_list seed_gen'
  where
    (first_point, seed_gen') = plum_point seed_gen

然后,您只需将main中的代码修改为类似take 100 $ plum_data_list gen的内容,即可恢复线性性能。