九维我操你爹
如果对 debug 感兴趣,大家可以依次看我心目中最厉害的 debugger 的三个视频和一个播客,能学到非常多的东西:
1. Real World Debugging with eBPF
https://www.youtube.com/watch?v=nggZEwGLC-Q
2. eBPF for Python Troubleshooting
https://m.bilibili.com/video/BV1bJz9YTEGJ
3. gdb -p $(pidof python)
https://bilibili.com/video/BV121Wnz1ELm
4. 播客《和 Gray 聊聊那些年遇到的神奇 Bug》
https://pythonhunter.org/episodes/ep35
1. Real World Debugging with eBPF
https://www.youtube.com/watch?v=nggZEwGLC-Q
2. eBPF for Python Troubleshooting
https://m.bilibili.com/video/BV1bJz9YTEGJ
3. gdb -p $(pidof python)
https://bilibili.com/video/BV121Wnz1ELm
4. 播客《和 Gray 聊聊那些年遇到的神奇 Bug》
https://pythonhunter.org/episodes/ep35
saka 老师前几周分享的这篇文章 https://skoredin.pro/blog/golang/cpu-cache-friendly-go 非常有意思,我虽然日常在 pahole 输出里看到 cacheline,但对其如何影响程序运行的理解也非常模糊。
不过更有意思的是,我已经见到三位工程师在 AI 的辅助下试图测试 “cacheline padding 带来六倍性能提升” 却没有成功,最后吐槽这是一篇错文、AI文。这里有一个有趣的知识屏障,如果不理解 cacheline 在何时会影响性能,那就可能无法写出正确的测试程序;但无法写出正确的测试程序又无法理解 cacheline 如何影响性能,知识死锁了。
你以为我想说原文的 “cacheline导致六倍性能差距” 的结论是正确的?不,那是错误的 有前提条件的,并非通用结论🤪
这些对我也是新知识,水平有限,施工区域谨慎阅读🤬
我的测试代码不用很多工程师和 AI 用的 go bench 方法,因为抽象程度太高了,在这种性能施工区最好就写一眼能看穿汇编的简单代码。
提供了两套变式, 通过命令行的 arg1 和 arg2 指定是否 padding 和是否用 atomic.AddUint64。
我本地的 cpu 0,1 是同一个核心,1,2 是不同核心,所以测试命令是
很多细节我依然在学习中,目前可以公开的情报是:(+表示前者性能更好,-反之)
1. "pad atom" vs "nopad atom": +7.3倍性能
2. "pad nonatom" vs "nopad noatom": +3.3倍
3. "pad atom" vs "pad noatom": -2.5倍
4. "nopad atom" vs "nopad noatom": -5倍
可以看到 atom (lock prefix insn)本身就造成大量的性能影响,而 pad 会进一步加重 cacheline false sharing 导致更极端的性能差距。原文里的六倍性能差距是在 atom + pad 的场景下的测试结果,但我觉得大部分场景根本不会这么极端。
核心绑定情况也会造成很大影响。如果绑核改为 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
在这些测试里,经典的 perf topdown 方法在 L2->L3->L4 几乎完全失效,经常会看到 L2 的 tma_core_bound 40% 然后 tma_core_bound_group breakdown 是 0%、L3 的 tma_l3_bound 15% 然而 L4 的 tma_l3_bound_group 里面四个 0%。我的 cpu 型号是 x86 meteorlake,仔细看了 pmu metrics 定义之后我觉得这就是设计上的问题,L2 再往下没有保证,只能靠微指令法师的经验来跳跃性猜测和验证。
可以确定有用的 metrics 是
- tma_store_fwd_blk: atom vs noatom 性能差距的罪魁,lock prefix insn 阻止了 store forwarding (CSAPP $4.5.5.2) 导致性能大幅下降
- tma_false_sharing: cacheline 在多核心上共享时的 race。原文其实主要就是在讨论这个讨论的性能问题。
- tma_l3_bound: snoop hitm 的间接指标。
- L1-dcache-loads,L1-dcache-load-misses: cacheline racing 的间接指标。但由于 l1 miss 至少包含了 "cacheline 不在 L1" 和 "cacheline 在 L1 但是失效",这个 miss 率其实很容易误导。
如何从 metrics 找到源码:
perf list 文档写得不全,直接看内核里的 pmu-events/.../mtl-metrics.json,比如说 perf record -M tma_false_sharing 很高,json 文件里这一项的 PublicDescription 就会写
然后采样
然后可以可以画火焰图和栈回溯,但我现在喜欢 perf annotate -l --stdio 直接看指令
看到 87% 的 false sharing 都是由于 inc %rcx 导致的。
讲完方法论终于可以回到主题了,cacheline 如何影响性能:如果是一个线程共享数据 A 在多核上并行 ++,核心1 修改了 A,那么核心2 的 L1 缓存的包含 A 的 cacheline 就会失效,这就是 false sharing。对于共享数据 A 来说这不可避免,但是如果有另一个 独立 数据 B 和 A 在同一个 cacheline,那么 A 在多核上刷存在感就会导致 B 的 L1 cache 失效,尽管 B 可能完全是一个单线程非共享数据。好的 cacheline 设计可以加上一段 padding 把 B 强制隔离出 A 所在的 cacheline,这样 A 刷新 cacheline 不会导致 B 的 cacheline 失效。
这些知识真的非常晦涩难懂,资料很少,AI 基本在帮倒忙,我感觉像是在星际航行,目睹所有令人惊叹的宇宙奇观,恍惚间就化入无穷。
不过更有意思的是,我已经见到三位工程师在 AI 的辅助下试图测试 “cacheline padding 带来六倍性能提升” 却没有成功,最后吐槽这是一篇错文、AI文。这里有一个有趣的知识屏障,如果不理解 cacheline 在何时会影响性能,那就可能无法写出正确的测试程序;但无法写出正确的测试程序又无法理解 cacheline 如何影响性能,知识死锁了。
你以为我想说原文的 “cacheline导致六倍性能差距” 的结论是正确的?不,那是
这些对我也是新知识,水平有限,施工区域谨慎阅读
我的测试代码不用很多工程师和 AI 用的 go bench 方法,因为抽象程度太高了,在这种性能施工区最好就写一眼能看穿汇编的简单代码。
package main
import (
"fmt"
"os"
"sync"
"sync/atomic"
"time"
)
const N = 100_000_000
type Counters interface {
Inc(idx int)
AtomicInc(idx int)
Result(idx int) uint64
}
type unpaddingCounters [8]uint64
func (u *unpaddingCounters) Inc(idx int) {
u[idx]++
}
func (u *unpaddingCounters) AtomicInc(idx int) {
atomic.AddUint64(&u[idx], 1)
}
func (u *unpaddingCounters) Result(idx int) uint64 {
return u[idx]
}
type paddingCounter struct {
val uint64
_ [56]byte
}
type PaddingCounters [8]paddingCounter
func (p *PaddingCounters) Inc(idx int) {
p[idx].val++
}
func (p *PaddingCounters) AtomicInc(idx int) {
atomic.AddUint64(&p[idx].val, 1)
}
func (p *PaddingCounters) Result(idx int) uint64 {
return p[idx].val
}
func main() {
var counters Counters
if os.Args[1] == "pad" {
counters = &PaddingCounters{}
} else {
counters = &unpaddingCounters{}
}
var inc func(idx int)
if os.Args[2] == "atom" {
inc = counters.AtomicInc
} else {
inc = counters.Inc
}
start := time.Now()
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < N; j++ {
inc(i)
}
}()
}
wg.Wait()
fmt.Printf("Duration: %v ", time.Since(start))
for i := 0; i < 8; i++ {
fmt.Printf("Counter[%d]=%d ", i, counters.Result(i))
}
fmt.Println()
}
提供了两套变式, 通过命令行的 arg1 和 arg2 指定是否 padding 和是否用 atomic.AddUint64。
我本地的 cpu 0,1 是同一个核心,1,2 是不同核心,所以测试命令是
taskset -c 1,2 perf stat -d -- env GOMAXPROCS=2 ./go_cpu_perf pad atom很多细节我依然在学习中,目前可以公开的情报是:(+表示前者性能更好,-反之)
1. "pad atom" vs "nopad atom": +7.3倍性能
2. "pad nonatom" vs "nopad noatom": +3.3倍
3. "pad atom" vs "pad noatom": -2.5倍
4. "nopad atom" vs "nopad noatom": -5倍
可以看到 atom (lock prefix insn)本身就造成大量的性能影响,而 pad 会进一步加重 cacheline false sharing 导致更极端的性能差距。原文里的六倍性能差距是在 atom + pad 的场景下的测试结果,但我觉得大部分场景根本不会这么极端。
核心绑定情况也会造成很大影响。如果绑核改为 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
在这些测试里,经典的 perf topdown 方法在 L2->L3->L4 几乎完全失效,经常会看到 L2 的 tma_core_bound 40% 然后 tma_core_bound_group breakdown 是 0%、L3 的 tma_l3_bound 15% 然而 L4 的 tma_l3_bound_group 里面四个 0%。我的 cpu 型号是 x86 meteorlake,仔细看了 pmu metrics 定义之后我觉得这就是设计上的问题,L2 再往下没有保证,只能靠微指令法师的经验来跳跃性猜测和验证。
可以确定有用的 metrics 是
- tma_store_fwd_blk: atom vs noatom 性能差距的罪魁,lock prefix insn 阻止了 store forwarding (CSAPP $4.5.5.2) 导致性能大幅下降
- tma_false_sharing: cacheline 在多核心上共享时的 race。原文其实主要就是在讨论这个讨论的性能问题。
- tma_l3_bound: snoop hitm 的间接指标。
- L1-dcache-loads,L1-dcache-load-misses: cacheline racing 的间接指标。但由于 l1 miss 至少包含了 "cacheline 不在 L1" 和 "cacheline 在 L1 但是失效",这个 miss 率其实很容易误导。
如何从 metrics 找到源码:
perf list 文档写得不全,直接看内核里的 pmu-events/.../mtl-metrics.json,比如说 perf record -M tma_false_sharing 很高,json 文件里这一项的 PublicDescription 就会写
Sample with: OCR.DEMAND_RFO.L3_HIT.SNOOP_HITM然后采样
perf record -F9999 -g -e OCR.DEMAND_RFO.L3_HIT.SNOOP_HITM然后可以可以画火焰图和栈回溯,但我现在喜欢 perf annotate -l --stdio 直接看指令
0.42 : 4949aa: inc %rcx
: 42 inc(i)
87.08 : 4949ad: mov 0x18(%rsp),%rax看到 87% 的 false sharing 都是由于 inc %rcx 导致的。
讲完方法论终于可以回到主题了,cacheline 如何影响性能:如果是一个线程共享数据 A 在多核上并行 ++,核心1 修改了 A,那么核心2 的 L1 缓存的包含 A 的 cacheline 就会失效,这就是 false sharing。对于共享数据 A 来说这不可避免,但是如果有另一个 独立 数据 B 和 A 在同一个 cacheline,那么 A 在多核上刷存在感就会导致 B 的 L1 cache 失效,尽管 B 可能完全是一个单线程非共享数据。好的 cacheline 设计可以加上一段 padding 把 B 强制隔离出 A 所在的 cacheline,这样 A 刷新 cacheline 不会导致 B 的 cacheline 失效。
这些知识真的非常晦涩难懂,资料很少,AI 基本在帮倒忙,我感觉像是在星际航行,目睹所有令人惊叹的宇宙奇观,恍惚间就化入无穷。
fedora 大概不会有官方的 systemd-cron 了
就算用 copr 源,不说原作者会不会继续维护,至少目前作者最新的解决方案反而导致了 42 升级 43 失败
对后来人,我建议在 fedora 上自己编译安装 systemd-cron
https://github.com/tindy2013/subconverter/pull/871
提 PR -> 嗯还是自己测试一下吧 -> 卧槽上游的 Dockerfile 这是什么鬼 -> 5 个小时没了😭( #mysophobia #FearDrivenDevelopment
总之自测是没啥问题了,还完善了上游没有的 smoke test
遇到 subconverter 给 Loon 客户端转换后的订阅缺少 Hysteria2 节点的情况可以用下面的镜像自己搭一个服务解决:
icecodexi/subconverter:latest
提 PR -> 嗯还是自己测试一下吧 -> 卧槽上游的 Dockerfile 这是什么鬼 -> 5 个小时没了😭( #mysophobia #FearDrivenDevelopment
总之自测是没啥问题了,还完善了上游没有的 smoke test
遇到 subconverter 给 Loon 客户端转换后的订阅缺少 Hysteria2 节点的情况可以用下面的镜像自己搭一个服务解决:
icecodexi/subconverter:latest
不知道有没有人和我一样,看到这个短语不禁又在心里骂日本人懂个屁的汉字了
但我当下没有发这个吐槽,是因为我真怕这件事其实是自己搞错了。所以一定要先查过了再骂
万幸看到现代汉语词典里是写的「好事多磨」,至少证明了我这些年没有写白字
那有没有可能是在简化汉字的过程中出了什么问题呢?查台湾教育部《成语典》来看,两种写法都有出处,不过写成「好事多磨」的出典要多很多
就拿红楼梦来说,里面就同时用到了这两种写法:
「那紅塵中卻有些樂事,但不能永遠依恃;況又有『美中不足,好事多魔』八個字緊相聯屬,瞬息間則又樂極悲生,人非物換。」
「想來寶玉和姑娘必是姻緣。人家說的,『好事多磨』,又說道『是姻緣棒打不回』。這樣看起來,人心天意,他們兩個竟是天配的了。」
此外还有好事多妨、好事多慳、好事多阻、好事天慳、美事多磨这么多种说法。看来真不是日本人又把汉字词学错了,而是「好事多磨」这个词还有许多的兄弟姐妹
https://dict.idioms.moe.edu.tw/idiomView.jsp?ID=-983
但我当下没有发这个吐槽,是因为我真怕这件事其实是自己搞错了。所以一定要先查过了再骂
万幸看到现代汉语词典里是写的「好事多磨」,至少证明了我这些年没有写白字
那有没有可能是在简化汉字的过程中出了什么问题呢?查台湾教育部《成语典》来看,两种写法都有出处,不过写成「好事多磨」的出典要多很多
就拿红楼梦来说,里面就同时用到了这两种写法:
「那紅塵中卻有些樂事,但不能永遠依恃;況又有『美中不足,好事多魔』八個字緊相聯屬,瞬息間則又樂極悲生,人非物換。」
「想來寶玉和姑娘必是姻緣。人家說的,『好事多磨』,又說道『是姻緣棒打不回』。這樣看起來,人心天意,他們兩個竟是天配的了。」
此外还有好事多妨、好事多慳、好事多阻、好事天慳、美事多磨这么多种说法。看来真不是日本人又把汉字词学错了,而是「好事多磨」这个词还有许多的兄弟姐妹
https://dict.idioms.moe.edu.tw/idiomView.jsp?ID=-983
昨天 Grammarly 正式宣布公司改名为 Superhuman 了。
刚看到这个通知的时候我还以为是它被收购了,没想到其实是它把之前收购的一些产品做了整合,还推出了新产品。
看了介绍,主推还是办公自动化,主动式 AI 辅助。不过感觉还是有点同质化,相当于和 m365 copilot/Gemini/notion 抢生意
https://www.grammarly.com/blog/company/introducing-new-superhuman/
刚看到这个通知的时候我还以为是它被收购了,没想到其实是它把之前收购的一些产品做了整合,还推出了新产品。
看了介绍,主推还是办公自动化,主动式 AI 辅助。不过感觉还是有点同质化,相当于和 m365 copilot/Gemini/notion 抢生意
https://www.grammarly.com/blog/company/introducing-new-superhuman/