126

如何找到在 Haskell(主要是 GHC)中存储某种数据类型的值所需的实际内存量?是否可以在运行时评估它(例如在 GHCi 中),或者是否可以从其组件估计复合数据类型的内存需求?

一般来说,如果类型a和内存需求b已知,那么代数数据类型的内存开销是多少,例如:

data Uno = Uno a
data Due = Due a b

例如,这些值在内存中占用了多少字节?

1 :: Int8
1 :: Integer
2^100 :: Integer
\x -> x + 1
(1 :: Int8, 2 :: Int8)
[1] :: [Int8]
Just (1 :: Int8)
Nothing

我知道由于垃圾收集延迟,实际内存分配更高。由于惰性评估,它可能会有很大的不同(并且 thunk 大小与值的大小无关)。问题是,给定一个数据类型,它的值在完全评估时需要多少内存?

我发现 GHCi 中有一个:set +s选项可以查看内存统计信息,但不清楚如何估计单个值的内存占用量。

4

2 回答 2

158

(以下适用于 GHC,其他编译器可能使用不同的存储约定)

经验法则:构造函数的标题一个字,每个字段一个字。例外:没有字段(如Nothingor True)的构造函数不占用空间,因为 GHC 创建了这些构造函数的单个实例并在所有用途中共享它。

一个字在 32 位机器上是 4 个字节,在 64 位机器上是 8 个字节。

所以例如

data Uno = Uno a
data Due = Due a b

anUno需要 2 个单词,aDue需要 3 个单词。

Int类型定义为

data Int = I# Int#

现在,Int#需要一个单词,所以Int总共需要 2 个。大多数未装箱的类型占用一个单词,例外是Int64#, Word64#, and Double#(在 32 位机器上)占用 2。GHC 实际上有一个类型为Intand的小值的缓存Char,因此在许多情况下,它们根本不占用堆空间。AString只需要列表单元格的空间,除非您使用Chars > 255。

AnInt8与 具有相同的表示IntInteger定义如下:

data Integer
  = S# Int#                            -- small integers
  | J# Int# ByteArray#                 -- large integers

所以一个小的Integer( S#) 占用 2 个单词,但一个大的整数占用可变数量的空间,具体取决于它的值。AByteArray#需要 2 个字(标题 + 大小)加上数组本身的空间。

请注意,用 定义的构造函数newtype是 freenewtype纯粹是一个编译时的想法,它在运行时不占用空间和指令。

GHC 评论中的堆对象布局中的更多详细信息。

于 2010-07-15T14:56:14.767 回答
5

ghc-datasize 包提供recursiveSize函数来计算 GHC 对象的大小。然而...

在计算大小之前执行垃圾收集,因为垃圾收集器会使堆遍历变得困难。

......所以经常打电话是不切实际的!

另请参阅如何找出 GHC 的数据类型的内存表示?以及如何确定 Haskell 中类型的大小?.

于 2015-09-21T15:00:25.790 回答