总述

本文会分析gc中 writeBarrier、bitmap 、writeBuffer技术.

背景

写屏障这个技术是从 go1.5引入的, 是三色标记的gc需要的一个策略, 后来在go1.8中, 消除rescan的设计中进行了升级, 使用了混合屏障, 避免了rescan的STW的消耗. gc过程中, 除了写屏障技术, 在mark阶段也进行了 优化, 使用 bitmap 替换了sweep流程, 避免sweep带来的性能问题: 遍历堆、 缓存亲和力差.

分析

源码路径: mwbuf.go mbarrier.go

mbarrier

mbarrier.go的注释很全了. (和proposal一致) 通过注释知道, golang使用了 Yuasa-style deletetion barrier 和 Dijkstra insertion barrier. shade(染色)和condition work 一起工作避免了 修改器对gc隐藏了对象. 方法如下

  1. 当 从堆上unlink object 的时候, 进行染色, 将唯一的指针从堆上移动到栈上
  2. 当将指针 安装到 黑色对象, 进行染色. 将唯一指针从栈移动到 黑色对象.
  3. 一旦goroutine栈是黑色的, 就没必要 染色了

主要用来:

  1. 内存顺序的解决
  2. stack write
  3. global writes
  4. publication ordering
  5. singal handler pointer writes

在实现上, 是在类似memmove的操作前面

使用场景

mgcmark.go

mbitmap.go

writebuffer

每个p都有一个wirte buffer queue 作为 pool. 填满之后就会刷新到 gc workbuf.

数据结构

type wbBuf struct {
    // next points to the next slot in buf. It must not be a
    // pointer type because it can point past the end of buf and
    // must be updated without write barriers.
    //
    // This is a pointer rather than an index to optimize the
    // write barrier assembly.
    next uintptr

    // end points to just past the end of buf. It must not be a
    // pointer type because it points past the end of buf and must
    // be updated without write barriers.
    end uintptr

    // buf stores a series of pointers to execute write barriers
    // on. This must be a multiple of wbBufEntryPointers because
    // the write barrier only checks for overflow once per entry.
    buf [wbBufEntryPointers * wbBufEntries]uintptr
}

其实, next、end 都是指向 buf的 下一个和最后一个. 其中, 每次的pointer都是放两个指针: old、new. 在 wbBufFlush的实现中, 就是进行 染成灰色、标记.

使用场景

atomic.Value的实现和分析中, StorePointer会先调用 atomicwb: 在原子指针写之前 执行 写屏障操作.

其他

消除STW stack rescan的讨论

golang1.7 使用 Dijkstra write barrier 处理 并发指针修改. 因为 读取远大于写入, 所以性能好. 但是呢, golang 在stack write 的时候, 不是使用 stack write barrier, 而是 STW期间进行re scan. 这样的话, 如果有大量的活跃的goroutine, re scan stack 会消耗大概 10-100 毫秒的时间.
golang后面的版本采用 混合屏障策略, 所有新分配的对象都是 黑色的 (以前都是白色的), 这样, 就不需要进行 stack rescan, 从而节省了大量时间. hybrid barrier 通过初始scan 的时候stack标黑, 实现在标记阶段的时候, 允许并发scan, 相比而言, Dijkstra barrier 允许并发mark, 但是在 标记的最终时候->rescan stack 需要一次STW. Yuasa 在标记mark的一开始的时候, 需要一次 STW(获取stack快照), 但是在 mark最终的时候, 并不需要rescan. 参考文献: elimate rescan

参考

  1. 一个比较清晰的版本梳理