IORef 和 STRef 的编译
Compilation of IORef and STRef
为了衡量这些 Refs 的性能,我将 GHC 生成的程序集转储到以下代码中:
import Data.IORef
main = do
r <- newIORef 18
v <- readIORef r
print v
我希望 IORef 被完全优化掉,只留下一个系统调用来用字符串“18”写入标准输出。相反,我得到了 250 条装配线。你知道有多少人会真正被处决吗?这是我认为的程序的核心:
.globl Main.main1_info
Main.main1_info:
_c1Zi:
leaq -8(%rbp),%rax
cmpq %r15,%rax
jb _c1Zj
_c1Zk:
movq $block_c1Z9_info,-8(%rbp)
movl $Main.main2_closure+1,%ebx
addq $-8,%rbp
jmp stg_newMutVar#
_c1Zn:
movq ,904(%r13)
jmp stg_gc_unpt_r1
.align 8
.long S1Zo_srt-(block_c1Z9_info)+0
.long 0
.quad 0
.quad 30064771104
block_c1Z9_info:
_c1Z9:
addq ,%r12
cmpq 856(%r13),%r12
ja _c1Zn
_c1Zm:
movq 8(%rbx),%rax
movq $sat_s1Z2_info,-16(%r12)
movq %rax,(%r12)
movl $GHC.Types.True_closure+2,%edi
leaq -16(%r12),%rsi
movl $GHC.IO.Handle.FD.stdout_closure,%r14d
addq ,%rbp
jmp GHC.IO.Handle.Text.hPutStr2_info
_c1Zj:
movl $Main.main1_closure,%ebx
jmp *-8(%r13)
我很关心这个 jmp stg_newMutVar#
。它在程序集中无处可寻,因此 GHC 可能会在稍后的链接阶段解决它。但它为什么会在这里,它有什么作用?我可以在没有任何未解析的 haskell 符号的情况下转储最终程序集吗?
从几个链接开始:
- The
MutVar
object definition.
- The
cmm
code for newMutVar
.
- A non-comprehensive but helpful summary of GHC object layout.
cmm
and C
sources aren't particularly readable if you're not already familiar with the macros and primops. Unfortunately, I don't know of a good way to view the assembly generated for cmm
primops, short of looking into an executable with objdump 或其他一些反汇编程序。
不过,我可以总结一下 IORef
的运行时语义。
IORef
is a wrapper around MutVar#
来自 GHC.Prim
。正如文档所说,MutVar#
就像一个 single-element 可变数组。它占用两个机器字,第一个是 header,第二个是存储的值(它是指向 GHC object 的指针)。 MutVar#
的值本身就是指向此 two-word object 的指针。
MutVar
-s 与普通的不可变 objects 的不同之处在于参与了写屏障机制。 GHC 有分代垃圾collection,所以在老一代中的任何 MutVar
在收集年轻一代时也必须也是 GC 根,因为改变 MutVar
可能会导致更年轻的 object 变得可达。因此,每当从第 0 代(最年轻的)提升 MutVar
时,它都会添加到包含对所有此类可变 object 的引用的 so-called "mutable list" 中。可变列表在老年代的 GC 期间重建。简而言之,老年代的 MutVar
-s 总是出现在可变列表中。
这是处理可变变量的一种相当简单的方法,如果我们在老年代有大量可变变量,次要垃圾 collection 会因为膨胀的可变列表而变慢,结果entire program slows down.
由于可变变量在生产代码中的使用并不突出,因此没有太多的需求或压力来优化 RTS 的大量使用。
如果您需要大量可变变量,您应该使用单个可变盒装数组,因为它只是可变列表上的单个引用,并且还有一个 bitmap-based optimization 用于 GC 遍历的元素可能已经变异了。
此外,如您所见,newMutVar#
只是静态链接而不是内联,尽管它是一小段代码。结果,它也没有被优化掉。这再次广泛地是因为缺乏对优化变异代码的努力和关注。相比之下,分配和复制小型 known-sized 基本数组目前是 inlined and greatly optimized, because Johan Tibell who did large amount of work implementing the unordered-containers
库实现的(为了使 unordered-containers
更快)。
为了衡量这些 Refs 的性能,我将 GHC 生成的程序集转储到以下代码中:
import Data.IORef
main = do
r <- newIORef 18
v <- readIORef r
print v
我希望 IORef 被完全优化掉,只留下一个系统调用来用字符串“18”写入标准输出。相反,我得到了 250 条装配线。你知道有多少人会真正被处决吗?这是我认为的程序的核心:
.globl Main.main1_info
Main.main1_info:
_c1Zi:
leaq -8(%rbp),%rax
cmpq %r15,%rax
jb _c1Zj
_c1Zk:
movq $block_c1Z9_info,-8(%rbp)
movl $Main.main2_closure+1,%ebx
addq $-8,%rbp
jmp stg_newMutVar#
_c1Zn:
movq ,904(%r13)
jmp stg_gc_unpt_r1
.align 8
.long S1Zo_srt-(block_c1Z9_info)+0
.long 0
.quad 0
.quad 30064771104
block_c1Z9_info:
_c1Z9:
addq ,%r12
cmpq 856(%r13),%r12
ja _c1Zn
_c1Zm:
movq 8(%rbx),%rax
movq $sat_s1Z2_info,-16(%r12)
movq %rax,(%r12)
movl $GHC.Types.True_closure+2,%edi
leaq -16(%r12),%rsi
movl $GHC.IO.Handle.FD.stdout_closure,%r14d
addq ,%rbp
jmp GHC.IO.Handle.Text.hPutStr2_info
_c1Zj:
movl $Main.main1_closure,%ebx
jmp *-8(%r13)
我很关心这个 jmp stg_newMutVar#
。它在程序集中无处可寻,因此 GHC 可能会在稍后的链接阶段解决它。但它为什么会在这里,它有什么作用?我可以在没有任何未解析的 haskell 符号的情况下转储最终程序集吗?
从几个链接开始:
- The
MutVar
object definition. - The
cmm
code fornewMutVar
. - A non-comprehensive but helpful summary of GHC object layout.
cmm
and C
sources aren't particularly readable if you're not already familiar with the macros and primops. Unfortunately, I don't know of a good way to view the assembly generated for cmm
primops, short of looking into an executable with objdump 或其他一些反汇编程序。
不过,我可以总结一下 IORef
的运行时语义。
IORef
is a wrapper around MutVar#
来自 GHC.Prim
。正如文档所说,MutVar#
就像一个 single-element 可变数组。它占用两个机器字,第一个是 header,第二个是存储的值(它是指向 GHC object 的指针)。 MutVar#
的值本身就是指向此 two-word object 的指针。
MutVar
-s 与普通的不可变 objects 的不同之处在于参与了写屏障机制。 GHC 有分代垃圾collection,所以在老一代中的任何 MutVar
在收集年轻一代时也必须也是 GC 根,因为改变 MutVar
可能会导致更年轻的 object 变得可达。因此,每当从第 0 代(最年轻的)提升 MutVar
时,它都会添加到包含对所有此类可变 object 的引用的 so-called "mutable list" 中。可变列表在老年代的 GC 期间重建。简而言之,老年代的 MutVar
-s 总是出现在可变列表中。
这是处理可变变量的一种相当简单的方法,如果我们在老年代有大量可变变量,次要垃圾 collection 会因为膨胀的可变列表而变慢,结果entire program slows down.
由于可变变量在生产代码中的使用并不突出,因此没有太多的需求或压力来优化 RTS 的大量使用。
如果您需要大量可变变量,您应该使用单个可变盒装数组,因为它只是可变列表上的单个引用,并且还有一个 bitmap-based optimization 用于 GC 遍历的元素可能已经变异了。
此外,如您所见,newMutVar#
只是静态链接而不是内联,尽管它是一小段代码。结果,它也没有被优化掉。这再次广泛地是因为缺乏对优化变异代码的努力和关注。相比之下,分配和复制小型 known-sized 基本数组目前是 inlined and greatly optimized, because Johan Tibell who did large amount of work implementing the unordered-containers
库实现的(为了使 unordered-containers
更快)。