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 符号的情况下转储最终程序集吗?

从几个链接开始:

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 更快)。