IORef和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 $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符号的最终程序集?

从几个链接开始:

  • MutVar对象定义
  • newMutVarcmm代码。
  • 一个不全面但有用的GHC对象布局摘要。

cmmC源代码不是特别可读,如果你还不熟悉宏和primops。不幸的是,我不知道有什么好方法来查看为cmm primops生成的程序集,除非使用objdump或其他反汇编器查看可执行文件。

我仍然可以总结IORef的运行时语义。

IORefGHC.PrimMutVar#的包装。正如文档所说,MutVar#就像一个单元素可变数组。它占用两个机器字,第一个是头,第二个是存储值(它是一个指向GHC对象的指针)。值MutVar#本身就是指向这个双字对象的指针。

MutVar -s不同于普通的不可变对象,最明显的是它参与了写屏障机制。GHC具有分代垃圾收集,因此在收集年轻代时,任何位于老一代中的MutVar必须也是GC根,因为改变MutVar可能会导致年轻对象变得可访问。因此,每当MutVar从第0代(最年轻的)提升时,它就被添加到一个所谓的"可变列表"中,该列表包含对所有此类可变对象的引用。在旧代的GC期间重新构建可变列表。简而言之,老一代的MutVar -s总是存在于可变列表中。

这是一种相当简单的处理可变变量的方法,如果我们在老一代中有大量的可变变量,小的垃圾收集会因为膨胀的可变列表而变慢,从而导致整个程序变慢。

由于可变变量在产品代码中并不重要,因此没有太多的需求或压力来优化RTS,以适应它们的大量使用。

如果你需要大量的可变变量,你应该使用单个可变的盒装数组,因为这只是对可变列表的单个引用,并且对于可能已经发生变化的元素的GC遍历具有基于位图的优化。

此外,正如您所看到的,newMutVar#只是静态链接而不是内联的,尽管它是相当小的代码块。因此,它也没有被优化掉。这也是由于缺乏优化变异代码的努力和关注。相比之下,分配和复制已知大小的小原语数组目前是内联的,并且得到了极大的优化,因为Johan Tibell做了大量的工作来实现unordered-containers库(为了使unordered-containers更快)。

相关内容

  • 没有找到相关文章

最新更新