多线程编程之消除【伪共享】
调试多线程代码,性能一直上不去,今天使用 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 对齐规则。