多线程编程之消除【伪共享】

调试多线程代码,性能一直上不去,今天使用 pipe 性能差的出奇,想要找一份现成的环形队列(一读多写)来代替,结果有了大收获。

多线程下的面向cache优化

之前调性能的思路,基本和 《论程序底层优化的一些方法与技巧》 指出的一样:

总结起来,对于CPU缓存类型的优化,我们编写代码的大体原则就是:本来就有关联的东西(变量、代码),尽量在代码组织上让它们紧密地联系起来。本来毫无关联的东西,就应该把它们相分隔开。这不单单是可以让我们的代码更加有效率,同样也是编写更优美的代码的基本原则。

然而同样是面向cache做优化,多线程优化的关注点却不一样,特别关注消除【伪共享】。
以线程安全的环形队列为例,其数据结构包括一个指针,两个索引 in 和 out。如果这三个数据紧密排列,放在同一个缓存行的话,一个线程修改了 in 或者 out,其他线程即使不需要操作这个成员,L1 缓存也会失效,导致性能下降。然而这三个数据其实本身是独立修改的,不应该互相影响。解决办法也很简单,重新排列数据结构,或者直接加padding,保证不在一个 cacheline(64B)里就可以了,比如 jdk1.8加入的新注解@Contended就可以自动做到这一点。

一个值得关注的问题:

pthread 数据类型的大小

arch x86 amd64
pthread_mtx_t 24B 40B
pthread_rwlock_t 32B 56B
pthread_spinlock_t 4B 4B

其中, spinlock 其实是一个 volatile int,不存在任何填充;而其他类型都是 union,可以看到填充数据类型的大小。本机64位系统里的 mutex 定义如下:

typedef union
{
  struct __pthread_mutex_s __data;
  char __size[__SIZEOF_PTHREAD_MUTEX_T];
  long int __align;
} pthread_mutex_t;

这里 __align 的作用是保证这个 union 的对其方式是按照 long int 来对齐,从名字也可以看出来;__size 做的是 padding 工作,__SIZEOF_PTHREAD_MUTEX_T 的值就是 40;只有 __data 才是 pthread_mtx_t的具体实现。

ps: struct 或者 union 的成员变量按照定义的顺序来排列,可以直观分析其对齐问题;全局变量的相对顺序会在编译时进行调整,并不按照定义顺序排列,不能简单照搬 struct 对齐规则。

参考

论程序底层优化的一些方法与技巧 | 成都七中 骆可强

一种极致性能的缓冲队列 | 捉虫大师

高性能队列——Disruptor | 美团技术团队

posted @ 2022-02-24 16:12  与MPI做斗争  阅读(88)  评论(0编辑  收藏  举报