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符号的情况下转储最终的程序集吗?
从几个链接开始:> The MutVar
object definition.
> The cmm
code for newMutVar
.
> A non-comprehensive but helpful summary of GHC object layout.
如果您还不熟悉macros和primops,则cmm
和C源代码不是特别易读.不幸的是,我不知道查看为cmm primops生成的程序集的好方法,而不是查看可执行文件objdump或其他一些反汇编程序.
不过,我可以总结一下IORef的运行时语义.
IORef
是GHC.Prim在MutVar#
左右的包装.正如文档所说,MutVar#就像一个单元素的可变数组.它占用两个机器字,第一个是标题,第二个是存储值(它是指向GHC对象的指针). MutVar#的值本身就是指向这个双字对象的指针.
MutVar-s与普通的不可变对象不同,最显着的是通过参与写屏障机制. GHC具有分代垃圾收集功能,因此任何生活在老一代的MutVar在收集年轻代时都必须是GC根,因为改变MutVar可能会导致更年轻的对象变得可达.因此,每当从第0代(最年轻的)推广MutVar时,它就会被添加到所谓的“可变列表”中,该列表包含对所有此类可变对象的引用.可变列表在旧世代的GC期间重建.简而言之,老一代的MutVar-s总是存在于可变列表中.
这是处理可变变量的一种相当简单的方法,如果我们在旧代中有大量的变量,那么由于膨胀的可变列表而导致较小的垃圾收集速度减慢,结果是entire program slows down.
由于可变变量在生产代码中没有显着使用,因此对于RTS的大量使用进行优化的需求和压力并不大.
如果你需要大量的可变变量,你应该使用一个可变的盒装数组,因为这只是可变列表上的一个引用,并且还有一个bitmap-based optimization用于GC遍历可能已经变异的元素.
另外,正如您所看到的那样,newMutVar#只是静态链接但没有内联,尽管它只是一小块代码.因此,它也没有被优化掉.这又是因为缺乏优化变异代码的努力和注意力.相比之下,分配和复制小的已知大小的原始数组目前是inlined并且进行了大大优化,因为执行unordered-containers
库的大量工作的Johan Tibell就这样做了(为了使无序容器更快).