Haskell数据类型的内存占用量


124

如何找到在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的大小与值的大小无关)。问题是,在给定数据类型的情况下,充分评估其值会占用多少内存?

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

Answers:


156

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

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

一个字在32位计算机上为4个字节,在64位计算机上为8个字节。

所以例如

data Uno = Uno a
data Due = Due a b

一个Uno需要2个单词,一个Due需要3 个单词。

Int类型定义为

data Int = I# Int#

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

一个Int8具有相同的表示来IntInteger定义如下:

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

因此,小IntegerS#)占用2个字,但大整数根据其值占用可变的空间量。A ByteArray#需要2个字(标题+大小)加上数组本身的空间。

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

在“ GHC评论”中的“堆对象的布局”中有更多细节。


1
谢谢西蒙 这正是我想知道的。
萨斯坦宁

2
标头不是两个字吗?一个用于标记,另一个用于在GC或评估期间使用的转发指针?难道那不等于增加一个字吗?
爱德华·KMETT

5
@Edward:Thunk被间接寻址(随后由GC删除)覆盖,但是它们只有2个字,并且每个堆对象的大小至少应为两个2个字。没有打开任何分析或调试功能,标题实际上只是一个字。也就是说,在GHC中,其他实现可能会有所不同。
nominolo

3
nominolo:是的,但是来自Closure.h:/ *一个thunk有一个填充字来获取更新的值。这样一来,更新不会覆盖有效负载,因此我们可以避免在输入和更新期间需要锁定thunk。注意:这不适用于没有有效载荷的THUNK_STATIC。注意:我们会以各种方式(而不只是SMP)保留此填充词,因此我们不必为SMP重新编译所有库。* /负载在间接期间不会被覆盖。间接写在页眉中的单独位置。
爱德华·KMETT

6
是的,但是请注意,这仅适用于混音。它不适用于构造函数。无论如何,估计一个thunk的大小有点困难-您必须计算自由变量。
nominolo 2010年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.