2021SC@SDUSC
一、bvar的背景知识学习
????????bvar是多线程环境下的计数器类库,方便记录和查看用户程序中的各类数值,它利用了thread local存储减少了cache bouncing,相比UbMonitor(百度内的老计数器库)几乎不会给程序增加性能开销,也快于竞争频繁的原子操作。brpc集成了bvar,/vars可查看所有曝光的bvar,/vars/VARNAME可查阅某个bvar,在brpc中的使用方法请查看vars。brpc大量使用了bvar提供统计数值,当你需要在多线程环境中计数并展现时,应该第一时间想到bvar。但bvar不能代替所有的计数器,它的本质是把写时的竞争转移到了读:读得合并所有写过的线程中的数据,而不可避免地变慢了。当你读写都很频繁或得基于最新值做一些逻辑判断时,你不应该用bvar。
????????单看上面这一段gitee上关于bvar的介绍。对于我来说是很难理解到底bvar到底在框架中起到什么作用的,所以我又进行了更深入的学习。 ????????多核编程常用锁避免多个线程在修改同一个数据时产生race condition(竞争条件)。当锁成为性能瓶颈时,我们就想用另一种方式解决这种race condition,而不可避免地接触了原子指令(在操作系统课程中我们学习到了这种概念,即要么全做、要么全不做)。C++11正式引入了原子指令,几个常用的语法指令如下:
????????我们可以用这些指令做原子计数,比如多个线程同时累加一个原子变量,以统计这些线程对一些资源的操作次数。但是,这个操作没有想象中的快,可靠性也欠佳。 ????????于是,我们引出bvar的思路。 ????????没有任何竞争或只被一个线程访问的原子操作是比较快的,“竞争”指的是多个线程同时访问同一个cacheline。现代CPU为了以低价格获得高性能,大量使用了cache,并把cache分了多级。百度内常见的Intel E5-2620拥有32K的L1 dcache和icache,256K的L2 cache和15M的L3 cache。其中L1和L2 cache为每个核心独有,L3则所有核心共享。一个核心写入自己的L1 cache是极快的,但当另一个核心读或写同一处内存时,它得确认看到其他核心中对应的cacheline。对于软件来说,这个过程是原子的,不能在中间穿插其他代码,只能等待CPU完成一致性同步,这个复杂的硬件算法使得原子操作会变得很慢。访问被多个线程频繁共享的内存往往是比较慢的。 ????????要提高性能,就要避免让CPU频繁同步cacheline。这不单和原子指令本身的性能有关,还会影响到程序的整体性能。最有效的解决方法:尽量避免共享。 ????????其中的一个方法就是计数器,如果所有线程都频繁修改一个计数器,性能就会很差,原因同样在于不同的核心在不停地同步同一个cacheline。如果这个计数器只是用作打打日志之类的,那我们完全可以让每个线程修改thread-local变量,在需要时再合并所有线程中的值,性能可能有几十倍的差别。 ????????计数器的应用便是bvar。当很多线程都在累加一个计数器时,每个线程只累加私有的变量而不参与全局竞争,在读取时累加所有线程的私有变量。虽然读比之前慢多了,但由于这类计数器的读多为低频的记录和展现,慢点无所谓。而写就快多了,极小的开销使得用户可以无顾虑地使用bvar监控系统,这便是设计bvar的目的。
二、代码分析
总结
以上就是今天介绍的全部内容,本文仅仅简单介绍了BRPC的概念及安装编译,以及小组分工内容,之后的博客会进行对应代码的分析。
|