Post content
还记得我上一条对比过 pad+noatom vs nopad+noatom on same core 吗 核心绑定情况也会造成很大影响。如果绑核改为 0,1 cpu,它们是同一 core,测试结果是: 1. "pad atom" vs "nopad atom": +1.75 2. "pad noatom" vs "nopad noatom": +1.65 3. "pad atom" vs "pad noatom": -1.9 4. "nopad atom" vs "nopad noatom": -2.0 我做锻炼的时候越想越不对,这里不会有 false sharing 啊,怎么会有接近两倍的性能差距?而且既然没有 false sharing,那么 nopad 的 cache hit 应该更高,性能应该更好,怎么反而更慢了? 所以我全裸着开始新一轮 perf (爽!),发现 nopad+noatom 造成了大量的 tma_bad_speculation?立刻采样 taskset -c 0,1 perf record -F9999 -g -e MACHINE_CLEARS.COUNT -- env GOMAXPROCS=2 ./go_cpu_perf pad noatom 先看一下火焰图 (ASCII 版本) $ perf report -g --stdio 100.00% 0.00% go_cpu_perf go_cpu_perf [.] runtime.goexit.abi0 | ---runtime.goexit.abi0 main.main.func1 main.Counters.Inc-fm | --97.25%--main.(*PaddingCounters).Inc 97% 在 Inc() 函数里🤯anno 一下 $ perf annotate -l --stdio --symbol 'main.(*PaddingCounters).Inc' 0.00 : 4944ae: test %al,(%rax) 96.23 : 4944b0: shl $0x6,%rbx // go.go:0 0.00 : 4944b4: incq (%rax,%rbx,1) 96% 在 test %al,(%rax),这 tm 是在判断 method receiver 是否是空指针🤯 居然造成了巨型分支预测错误,想不到吧,每日一个高性能 golang 小技巧😊 改成 - func (u *unpaddingCounters) Inc(idx int) { + func (u unpaddingCounters) Inc(idx int) { u[idx]++ } 这下在单核心上 nopad 的 IPC 比 pad 更高了 (2.0 vs 1.75),但是 wall time 依然更慢一点? 两个月前 QC 教过我,IPC 更高但是跑得更慢可能是指令级并发更低, tma_ports_utilized_3m / tma_ports_utilized_2(edit: 错误的记忆,ILP 差会导致 IPC 低);但是这次并非如此,而是 pad 所需的指令数更少 (-21%😀) gdb disas 一下,发现是因为 unpaddingCounters 从 pointer receiver 改成了 value receiver 导致多一次值拷贝到栈上多了一大堆指令😀(然后我改了半天都降不下去,算了去做其他事情先) 计算机实在是太神奇了,我在这里找到归宿。