具有单个严格字段的现有数据类型
Existential data types with a single strict field
所以我有一个具有单个严格字段的存在数据类型:
data Uncurry (a :: i -> j -> *) (z :: (i,j)) =
forall x y. z ~ '(x,y) => Uncurry !(a x y)
使用 unsafeSizeof
进行的实验(从 this answer 中窃取)让我相信它可以是零内存开销:
λ p = (0, '[=11=]') :: (Int, Char)
λ q = Uncurry p
λ unsafeSizeof p
10
λ unsafeSizeof q
10
所以看起来 Uncurry
有点像 newtype
,仅在编译时使用。
这对我来说很有意义,因为相等断言不需要随身携带字典。
这是一个有效的解释吗? GHC(或 Haskell 报告)对此有任何保证吗?还是我只是走运?
编辑修复了一些细节:四边形为 8 个字节并解释了静态 link 字段。
我认为 unsafeSizeOf
不准确 并且 您误解了它的输出。请注意,它旨在仅显示顶级闭包的内存使用情况,而不是对象的总 space 使用情况。我认为,您看到的是 q
在 添加 到元组 p
中需要 10 个字节(而 p
在 p
中需要 10 个字节加到盒装Char
和盒装Int
)。此外,我的测试表明顶层构造函数实际上每个都需要 24 个字节(在 64 位架构上),即使 unsafeSizeOf
也为我报告了 10 个字节。
特别是,如果我使用 GHC 8.0.2 stack ghc -- -fforce-recomp -ddump-asm -dsuppress-all -O2 ZeroMemory.hs
编译以下测试程序:
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
module ZeroMemory where
data Uncurry (a :: i -> j -> *) (z :: (i, j)) =
forall x y . z ~ '(x,y) => Uncurry !(a x y)
q :: Uncurry (,) '(Int, Char)
q = Uncurry (0, '[=10=]')
r :: Uncurry (,) '(Int, Char)
r = Uncurry (1, '')
那么顶级 q
闭包的内存占用如下所示:
q_closure:
.quad Uncurry_static_info
.quad $s$WUncurry_$d~~_closure+1
.quad q1_closure+1
.quad 3
注意这里每个.quad
实际上是8个字节;它是旧式 16 位 "words" 的 "quad"。我相信这里最后的 quad
,值为 3,是 the GHC implementation commentary 中描述的 "static link field",因此不适用于 "typical" 堆分配对象。
所以,忽略这个最终字段,顶层 q
闭包的总大小是 24 个字节,它指的是 q1_closure
表示包含的元组:
q1_closure:
.quad (,)_static_info
.quad q3_closure+1
.quad q2_closure+1
.quad 3
另外 24 个字节。
q2
和 q3
闭包是盒装的 Int
和 Char
因此每个占用两个四边形(16 字节)。因此,q
总共占用 10 个四边形,即 80 个字节。 (我将 r
作为完整性检查包括在内,以确保我没有错误识别任何共享信息。)
一个 p
元组本身的内存占用相当于 q1_closure
,所以 7 个四边形或 56 个字节。
data
永远不会转换为 newtype
。 Uncurry
确实添加了一个新的闭包,并且从 GHC 8.0.2 开始,~
字典的指针实际上也被移动了。因此,Uncurry
有一个包含三个词的闭包。
unsafeSizeof
是不正确的,因为 Array#
以字存储其大小,而 ByteArray#
以字节存储其大小,因此 sizeofByteArray# (unsafeCoerce# ptrs)
returns 数字字数而不是预期的字节数。在 64 位系统上正确的版本应该是这样的:
unsafeSizeof :: a -> Int
unsafeSizeof !a =
case unpackClosure# a of
(# x, ptrs, nptrs #) ->
I# (8# +# sizeofArray# nptrs *# 8# +# sizeofByteArray# ptrs)
但是请注意,unsafeSizeof
只给出了最顶层闭包的大小。因此,任何盒装元组的闭包大小都是 24
,这与 Uncurry t
的闭包大小一致,因为 Uncurry
有一个信息指针,~
的无用指针,以及元组字段的指针。这种巧合也适用于之前的错误 unsafeSizeof
实现。但是 Uncurry t
的总大小大于 t
.
所以我有一个具有单个严格字段的存在数据类型:
data Uncurry (a :: i -> j -> *) (z :: (i,j)) =
forall x y. z ~ '(x,y) => Uncurry !(a x y)
使用 unsafeSizeof
进行的实验(从 this answer 中窃取)让我相信它可以是零内存开销:
λ p = (0, '[=11=]') :: (Int, Char)
λ q = Uncurry p
λ unsafeSizeof p
10
λ unsafeSizeof q
10
所以看起来 Uncurry
有点像 newtype
,仅在编译时使用。
这对我来说很有意义,因为相等断言不需要随身携带字典。
这是一个有效的解释吗? GHC(或 Haskell 报告)对此有任何保证吗?还是我只是走运?
编辑修复了一些细节:四边形为 8 个字节并解释了静态 link 字段。
我认为 unsafeSizeOf
不准确 并且 您误解了它的输出。请注意,它旨在仅显示顶级闭包的内存使用情况,而不是对象的总 space 使用情况。我认为,您看到的是 q
在 添加 到元组 p
中需要 10 个字节(而 p
在 p
中需要 10 个字节加到盒装Char
和盒装Int
)。此外,我的测试表明顶层构造函数实际上每个都需要 24 个字节(在 64 位架构上),即使 unsafeSizeOf
也为我报告了 10 个字节。
特别是,如果我使用 GHC 8.0.2 stack ghc -- -fforce-recomp -ddump-asm -dsuppress-all -O2 ZeroMemory.hs
编译以下测试程序:
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
module ZeroMemory where
data Uncurry (a :: i -> j -> *) (z :: (i, j)) =
forall x y . z ~ '(x,y) => Uncurry !(a x y)
q :: Uncurry (,) '(Int, Char)
q = Uncurry (0, '[=10=]')
r :: Uncurry (,) '(Int, Char)
r = Uncurry (1, '')
那么顶级 q
闭包的内存占用如下所示:
q_closure:
.quad Uncurry_static_info
.quad $s$WUncurry_$d~~_closure+1
.quad q1_closure+1
.quad 3
注意这里每个.quad
实际上是8个字节;它是旧式 16 位 "words" 的 "quad"。我相信这里最后的 quad
,值为 3,是 the GHC implementation commentary 中描述的 "static link field",因此不适用于 "typical" 堆分配对象。
所以,忽略这个最终字段,顶层 q
闭包的总大小是 24 个字节,它指的是 q1_closure
表示包含的元组:
q1_closure:
.quad (,)_static_info
.quad q3_closure+1
.quad q2_closure+1
.quad 3
另外 24 个字节。
q2
和 q3
闭包是盒装的 Int
和 Char
因此每个占用两个四边形(16 字节)。因此,q
总共占用 10 个四边形,即 80 个字节。 (我将 r
作为完整性检查包括在内,以确保我没有错误识别任何共享信息。)
一个 p
元组本身的内存占用相当于 q1_closure
,所以 7 个四边形或 56 个字节。
data
永远不会转换为 newtype
。 Uncurry
确实添加了一个新的闭包,并且从 GHC 8.0.2 开始,~
字典的指针实际上也被移动了。因此,Uncurry
有一个包含三个词的闭包。
unsafeSizeof
是不正确的,因为 Array#
以字存储其大小,而 ByteArray#
以字节存储其大小,因此 sizeofByteArray# (unsafeCoerce# ptrs)
returns 数字字数而不是预期的字节数。在 64 位系统上正确的版本应该是这样的:
unsafeSizeof :: a -> Int
unsafeSizeof !a =
case unpackClosure# a of
(# x, ptrs, nptrs #) ->
I# (8# +# sizeofArray# nptrs *# 8# +# sizeofByteArray# ptrs)
但是请注意,unsafeSizeof
只给出了最顶层闭包的大小。因此,任何盒装元组的闭包大小都是 24
,这与 Uncurry t
的闭包大小一致,因为 Uncurry
有一个信息指针,~
的无用指针,以及元组字段的指针。这种巧合也适用于之前的错误 unsafeSizeof
实现。但是 Uncurry t
的总大小大于 t
.