haskell - 将 "type"用作类型同义词会导致内存泄漏?

标签 haskell memory-leaks type-systems

我有以下代码作为我的热循环。

{-# LANGUAGE BangPatterns #-}
module Simulation.Simulator where

import Simulation.Common ()
import System.Random (RandomGen)
import Control.Monad.Random (Rand)

simulateUntil :: 
        (RandomGen g) =>
        Int                             ->      --Number of simulation steps
        a                               ->      --Initial model
        (a -> Rand g Bool)              ->      --Function to check if simulation should end
        (Float -> a -> Rand g a)        ->      --Update model one step
        Rand g a 
simulateUntil !num !model !done !update = do
        !stop <- done model
        if stop then return model 
        else do updateM <- update (1 / fromIntegral num) model
                simulateUntil num updateM done update

为了尝试使这个循环更具可读性并与我的其余代码更加内联,我在我的 Simulation.Common 代码中添加了一个类型同义词:

type SearchEnv a = (RandomGen g) => Rand g a

然后我更改了上面的循环以使用我所有其他代码中使用的这个新类型同义词,新循环几乎相同:

{-# LANGUAGE BangPatterns #-}
module Simulation.Simulator where

import Simulation.Common

simulateUntil ::
        Int                             ->      --Number of simulation steps
        a                               ->      --Initial model
        (a -> SeachEnv Bool)              ->      --Function to check if simulation should end
        (Float -> a -> SearchEnv a)        ->      --Update model one step
        SearchEnv a 
simulateUntil !num !model !done !update = do
        !stop <- done model
        if stop then return model 
        else do updateM <- update (1 / fromIntegral num) model
                simulateUntil num updateM done update

但由于某些原因,最后一个版本会泄漏内存,显示为

FUN_1_0

在 GHC 中使用“-h”选项运行时。

这是“类型”的预期行为还是有其他事情发生?

编辑: 这是 GHC“-h”选项报告的内存使用差异: 使用类型同义词:http://i.imgur.com/X1HiUQp.png 删除类型同义词后(恢复到顶部显​​示的旧代码):http://i.imgur.com/FuC863z.png

最佳答案

在内部,GHC 将类型类约束表示为类型类字典形式的函数参数。因此,如果您有一个类型 RandomGen g => Rand g a,它实际上会变成 RandomGen -> Rand a。这意味着无论何时使用调用doneupdate 的结果,都必须重新计算内部类型类函数。顺便说一下,这也意味着结果无法共享,因为 GHC 不会自动内存函数。在内部,doneupdate 的类型有点像这样:

done   :: a          -> (RandomGen -> Rand Bool)
update :: Float -> a -> (RandomGen -> Rand a)

我认为具体问题是您将 update 的结果传回递归调用,每次需要该值时,都必须调用具有类型类字典的内部函数.

在您的第一个版本中,RandomGen 类型字典被传递给顶级函数,因此除此之外不需要调用额外的“隐藏”函数。

GHC 通常很擅长优化这种事情,但我怀疑这是罪魁祸首。

这是一个更简单的实际例子。我们可以使用 :set +s 命令观察在 GHCI 中计算表达式所花费的时间和内存:

λ> let fib n = if n <= 1 then 1 else fib (n-1) + fib (n-2)
λ> let { n :: Num a => a; n = fib 30 }
λ> let { m :: Int; m = fib 30 }
λ> :set +s
λ> m
1346269
(2.04 secs, 695163424 bytes)
λ> m
1346269
(0.00 secs, 1073792 bytes)
λ> 
λ> n
1346269
(2.01 secs, 669035464 bytes)
λ> n
1346269
(2.02 secs, 669032064 bytes)

这是另一个例子。这有点像 fib 函数,只是它在每个递归调用中添加了一些常量。

λ> let { fib1 :: (Num a, Ord a, Num b) => Int -> a -> b;                fib1 m n = if n <= 1 then 1 else fromIntegral m + fib1 m (n-1) + fib1 m (n-2) }
λ> let { fib2 :: Int -> ((Num a, Ord a) => a) -> ((Num a, Ord a) => a); fib2 m n = if n <= 1 then 1 else fromIntegral m + fib2 m (n-1) + fib2 m (n-2) }
λ> :set +s
λ> fib1 1 30
2692537
(2.59 secs, 993139888 bytes)
λ> fib2 1 30
2692537
(17.98 secs, 7884453496 bytes)

由于 m 在第二个 fib 定义中变成了一个函数,所以每次需要时都必须调用它,因此不会发生共享,这会导致时间和空间泄漏。

关于haskell - 将 "type"用作类型同义词会导致内存泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23064358/

相关文章:

c++ - Eigen 稀疏矩阵乘法的内存泄漏

java - AWS 高级运行状况报告显示内存警告 - 是我的 Java 内存设置造成的吗?

c - C 中类似 Haskell 的类型系统

datetime - 如何在 Haskell 中将 UTCTime 格式化为 ISO 8601

algorithm - 我的 RSA 算法只适用于非常短的单词

c# - 是否有必要释放从 C# 接收到的 C++ 字符串的内存?

Javascript 运行时类型检查引擎

typescript :如何解释扩展和函数类型之间的这种交互

haskell - 这些类似 Free 的结构有一个概括吗?

haskell - 使用利修斯+哈姆雷特+朱利叶斯作为仆人