我一直在尝试在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秒的恒定空间运行。