如何找到在 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
选项可以查看内存统计信息,但不清楚如何估计单个值的内存占用量。
(以下适用于 GHC,其他编译器可能使用不同的存储约定)
经验法则:一个构造函数的标题是一个字,每个字段一个字。例外:没有字段的构造函数(如 Nothing
或 True
)不占用空间,因为 GHC 创建这些构造函数的单个实例并在所有使用中共享它。
一个字在 32 位机器上是 4 个字节,在 64 位机器上是 8 个字节。
所以例如
data Uno = Uno a
data Due = Due a b
Uno
占用 2 个单词,而 Due
占用 3 个单词。
Int
类型定义为
data Int = I# Int#
现在,Int#
占用 1 个单词,因此 Int
总共占用 2 个单词。大多数未装箱类型占用一个字,Int64#
、Word64#
和 Double#
(在 32 位机器上)例外,它们占用 2。GHC 实际上有一个 Int
和 {7 类型的小值的缓存},所以在很多情况下,它们根本不占用堆空间。 String
只需要列表单元格的空间,除非您使用 Char
s > 255.
Int8
与 Int
具有相同的表示。 Integer
定义如下:
data Integer
= S# Int# -- small integers
| J# Int# ByteArray# -- large integers
所以一个小的 Integer
(S#
) 占用 2 个单词,但一个大整数占用的空间量取决于它的值。 ByteArray#
需要 2 个字(标题 + 大小)加上数组本身的空间。
请注意,使用 newtype
定义的构造函数是免费的。 newtype
纯粹是一个编译时的想法,它在运行时不占用空间和指令。
The Layout of Heap Objects in the GHC Commentary 中有更多详细信息。
ghc-datasize 包提供 recursiveSize 函数来计算 GHC 对象的大小。然而...
在计算大小之前执行垃圾收集,因为垃圾收集器会使堆遍历变得困难。
......所以经常打电话是不切实际的!
另请参阅 How to find out GHC's memory representations of data types? 和 How can I determine size of a type in Haskell?。