我一直在尝试在Haskell中编码一种算法,该算法需要使用大量可变引用,但与纯惰性代码相比,它(可能并不奇怪)非常慢。
考虑一个非常简单的示例:

module Main where

import Data.IORef
import Control.Monad
import Control.Monad.Identity

list :: [Int]
list = [1..10^6]

main1 = mapM newIORef list >>= mapM readIORef >>= print
main2 = print $ map runIdentity $ map Identity list

在我的机器上运行GHC 7.8.2时,main1占用1.2s占用290MB内存,而main2只需0.4s占用1MB。是否有任何技巧可以阻止这种增长,特别是在太空中?与IORef不同,我经常需要针对非基本类型的Int,并假定IORef将使用一个额外的指针,就像常规的thunk一样,但是我的直觉似乎是错误的。

我已经尝试了使用未打包的IORef的专用列表类型,但是没有显着差异。

最佳答案

问题是您使用了mapM,它在时间和空间上在大型列表上始终表现不佳。正确的解决方案是使用mapM_(>=>)融合中间列表:

import Data.IORef
import Control.Monad

list :: [Int]
list = [1..10^6]

main = mapM_ (newIORef >=> readIORef >=> print) list

它在恒定的空间中运行,并提供出色的性能,在我的计算机上以0.4秒的速度运行。

编辑:在回答您的问题时,您也可以使用pipes来执行此操作,以避免必须手动融合循环:
import Data.IORef
import Pipes
import qualified Pipes.Prelude as Pipes

list :: [Int]
list = [1..10^6]

main = runEffect $
    each list >-> Pipes.mapM newIORef >-> Pipes.mapM readIORef >-> Pipes.print

这在我的计算机上以约0.7秒的恒定空间运行。

08-18 04:22