5

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

4

1 回答 1

11

从几个链接开始:

如果您还不熟悉primops,则cmmand源代码不是特别可读。不幸的是,除了使用objdump或其他反汇编程序查看可执行文件之外,我不知道查看为primops生成的程序集的好方法。Ccmm

不过,我可以总结一下IORef.

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

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

这是处理可变变量的一种相当简单的方式,如果我们在老年代有大量可变变量,那么由于可变列表臃肿,次要垃圾收集会变慢,从而导致整个程序变慢

由于可变变量在生产代码中的使用并不突出,因此针对大量使用优化 RTS 的需求或压力并不大。

如果您需要大量可变变量,则应改为使用单个可变盒装数组,因为这只是可变列表上的单个引用,并且还具有基于位图的优化,用于 GC 遍历可能已更改的元素。

此外,如您所见newMutVar#,它只是静态链接而不是内联,尽管它是一小段代码。结果,它也没有被优化掉。这又是因为缺乏优化变异代码的努力和关注。相比之下,分配和复制小的已知大小的原始数组目前是内联的并且得到了极大的优化,因为在实现库方面做了大量工作的Johan Tibellunordered-containers做到了(为了unordered-containers更快)。

于 2016-08-29T10:23:21.850 回答