

假共享内存(False Sharing)
source link: https://zhuanlan.zhihu.com/p/210665963
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

假共享内存(False Sharing)
(本文已经迁移备份到gitee项目中,不再为知乎提供内容,但对于本文,由于讨论区和内容相关,所以内容仍留在原地。后续全部内容都会迁移,完成后再删除帐号)
最近定位了一个问题,问题看来对很多写多线程的程序的工程师都有帮助,分享一下。
有一个多线程程序,业务分解为多个处理链路,Hash给每个线程去处理。但所有这些线程由操作系统任意调度的时候,比都绑在同一个核上性能慢一倍。
开发工程师用perf跟踪了一下,发现完成一样的业务,所使用指令的数量是一样的,但IPC的效率从0.53下降到了0.29。他们找不出原因让我们帮忙看看。
用perf record分析时间的瓶颈看不出什么问题,因为整个程序的函数时间分布很离散,最忙的函数也不过占用2%的时间。所以我们用perf stat直接收集整个执行效果,收集到的信息是这样的:
变化激烈的指标:
context-sw: 15k vs 11k
cycle: 5b vs 2.7b (1.847GHz)
bus_cycle: 5b vs 2b
l1d-refill: 20.4m vs 18.7m, 7.4M/s vs 12M/s
l1i-refill: 108m vs 86.4m, 39M/s vs 59M/s
l2d-refill: 83.4m vs 37.6m
l2i-refill: 45.9m vs 16.6m
前端stall: 3b vs 1.4b
后端stall: 1b vs 0.6b。
branch miss: 28m vs 15.8m
br_mis_pred: 28m vs 15m
基本不变的:
inst: 1.5b vs 1.4b
l1d_tlb: 800m vs 739m
l1i_tlb: 18.9m vs 17.9m
exec_ret/exec_taken: 164k vs 144k
mem_access: 663m vs 650m
pagefault: 0 vs 0
由这个情况推想,程序并没有因为调度到多个核上多走了分支,也没有多访问了内存,但总线访问却大幅增加,看起来是因为这些线程间有很多“共享内存”访问,导致了很多的核间Cache同步消息在bus上传播,(这不需要经过内存访问,参考:《内存访问模型》)从而导致CPU在前后端执行上都发生了stall,最终拖慢了整个IPC(但为什么会导致branch miss上升我是没有想明白,可能和微架构的OoO队列使用有关吧)。
排查代码,发现代码中有很多这样的per thread数据结构:
unit32 per_channel_data[MAX_CHANNEL_NUM];
消除这个问题,代码就不再需要绑核才能提供足够的性能了。
通常,写OS代码的人不容易犯这种错误,因为他清楚知道自己的代码是多CPU的的,他用的数据结构本来就是per_cpu的数据结构,自然会想到这是一种“共享内存”。但写应用程序的工程师很容易忘掉这个问题,因为对他来说这只是不同的线程,而他的线程并没有共享这些数据。
在今时今日,多线程基本上可以认为就是多CPU了,所以基本上,但凡这种per thread的数据接口,就要考虑离得远远的,不然就变成核间的“共享内存”了,而结果我们也看见了,性能可以100%这样下降的。
好吧,我承认,这个问题在鲲鹏920上会表现得更明显一点。因为鲲鹏使用多DIE设计,跨DIE的MESI协议(Cache同步的协议)会造成更明显的影响。这几乎是用更多核来强化性能的平台的通病,否则成本就得提高。所以,如果你对鲲鹏920做优化,尽量别让不同DIE上的线程共享内存,否则你我都会很难受:)。
而且我认为这是未来有更多的核的时候,所有人都要面对的问题:你总不能要求核间效率在你无限增加核的时候一直都能维持线性增长吧?
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK