IORef和STRef

时间:2016-08-29 09:25:47

标签: performance haskell ioref

为了测量那些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符号的情况下转储最终的程序集吗?

1 个答案:

答案 0 :(得分:10)

从几个链接开始:

如果您还不熟悉cmmmacros,那么primopsC来源就不会特别易读。不幸的是,我不知道查看为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更快。