bvar | bRPC

bvar 是多线程环境下的计数器类库,方便记录和查看用户程序中的各类数值。

特性

  • bvar 不能代替所有的计数器,它的本质是把写时的竞争转移到了读:读时需要合并所有写过的线程中的数据, 而不可避免地变慢了,所以读写都很频繁或需要基于最新值做一些逻辑判断时,不应该使用 bvar。
  • 当很多线程都在累加一个计数器时,每个线程只累加私有的变量而不参与全局竞争,在读取时累加所有线程的私有变量。虽然读比之前慢多了,但由于这类计数器的读多为低频的记录和展现,慢点无所谓。

相关知识

原子操作

原子操作有时会很慢。一个核心写入自己的 L1 cache 是极快的(4 cycles,约 2ns,但当另一个核心读或写同一处内存时,它得确认看到其他核心中对应的 cacheline。对于软件来说,这个过程是原子的,不能在中间穿插其他代码,只能等待 CPU 完成一致性同步,这个复杂的硬件算法使得原子操作会变得很慢。

cache bouncing

当很多线程在频繁修改某个字段时,这个字段所在的 cache line 被不停地同步到不同的核上,就像在核间弹来弹去,这个现象就叫做 cache bouncing。

memory fence

memory fence 简单来说就是串行化加载与存储操作,主要是为了防止 CPU 乱序执行导致的错误。以下面的代码为例。

1
2
3
4
5
6
7
8
9
// Thread 1
// ready was initialized to false
p.init();
ready = true;

// Thread2
if (ready) {
p.bar();
}

有一定的概率 Thread 1 中的指令被乱序执行,这时 Thread 2 中的指令就会在错误的情况下执行。boost 和 C++11 对 memory fence 做了抽象,上面的例子可以这么更正:

1
2
3
4
5
6
7
8
9
// Thread1
// ready was initialized to false
p.init();
ready.store(true, std::memory_order_release); // 前面访存指令勿重排至此条指令之后。当此条指令的结果对其他线程可见后,之前的所有指令都可见

// Thread2
if (ready.load(std::memory_order_acquire)) { // 后面访存指令勿重排至此条指令之前
p.bar();
}

参考

BRPC 官方文档
深入浅出cache写策略

作者

zhongtian

发布于

2020-05-19

更新于

2023-12-16

许可协议

评论