为了测量那些Refs的性能,我将GHC生成的组件转储到以下代码中:
import Data.IORef
main = do
r <- newIORef 18
v <- readIORef r
print v
我期望IORef完全被优化掉,只留下一个系统调用来写字符串“18”的stdout。相反,我得到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 $24,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 $24,%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 $8,%rbp
jmp GHC.IO.Handle.Text.hPutStr2_info
_c1Zj:
movl $Main.main1_closure,%ebx
jmp *-8(%r13)
我对此jmp stg_newMutVar#
感到担忧。它在集合中没有其他地方,所以GHC可能会在以后的链接阶段解决它。但为什么它甚至在这里,它做了什么?我可以在没有任何未解析的haskell符号的情况下转储最终的程序集吗?
答案 0 :(得分:10)
从几个链接开始:
MutVar
object definition. cmm
code for newMutVar
. 如果您还不熟悉cmm
和macros,那么primops和C
来源就不会特别易读。不幸的是,我不知道查看为cmm
primops生成的程序集的好方法,而不是查看objdump或其他反汇编程序的可执行文件。
但是,我可以总结IORef
的运行时语义。
IORef
是来自GHC.Prim
的{{3}}的包装器。正如文档所说,MutVar#
就像一个单元素的可变数组。它占用两个机器字,第一个是标题,第二个是存储值(它是指向GHC对象的指针)。值MutVar#
本身就是指向这个双字对象的指针。
MutVar
- s与普通的不可变对象不同,最显着的是通过参与写屏障机制。 GHC具有分代垃圾收集,因此生活在老一代的任何MutVar
在收集年轻一代时也必须是GC根,因为突变MutVar
可能导致年轻的对象变得可达。因此,每当从第0代(最年轻的)推广MutVar
时,它就会被添加到所谓的&#34;可变列表中。包含对所有此类可变对象的引用。可变列表在旧世代的GC期间重建。简而言之,旧代中的MutVar
- s总是存在于可变列表中。
这是处理可变变量的一种相当简单的方法,如果我们在旧代中有大量的变量,那么由于膨胀的可变列表而导致次要垃圾收集减慢,因此MutVar#
。
由于可变变量并未在生产代码中占据突出地位,因此对于大量使用RTS来优化RTS并没有太大的需求或压力。
如果你需要大量的可变变量,你应该使用一个可变的盒装数组,因为它只是可变列表上的一个引用,并且还有一个entire program slows down用于GC遍历可能已经发生变异的元素。
此外,正如您所看到的newMutVar#
只是静态链接但未内联,尽管它只是一小块代码。结果,它也没有被优化掉。这又是因为缺乏优化变异代码的努力和注意力。相比之下,分配和复制小的已知大小的原始数组目前bitmap-based optimization并进行了大大优化,因为执行inlined库的大量工作的Johan Tibell就是这样做的(为了让unordered-containers
更快。