如何在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
选项来查看内存统计信息,但目前尚不清楚如何估算单个值的内存占用量。
答案 0 :(得分:153)
(以下内容适用于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#
只占一个字,因此Int
总共需要2个字。大多数未装箱的类型只占一个字,例外是Int64#
,Word64#
和Double#
(在32位机器上),取2。GHC实际上有一个小值类型的缓存Int
和Char
,因此在很多情况下,它们根本不占用堆空间。 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
纯粹是一个编译时的想法,它不占用空间,并且在运行时不需要任何指令。
答案 1 :(得分:4)
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?。