TGTGInsighttelegram intelligenceLIVE / telegram public index
Back to channels
Welcome to the Black Parade avatar

TGINSIGHT CHAT

Welcome to the Black Parade

@TheB1ackParade

Music

Death has many faces, I look forward to seeing this one.

Subscribers787Current channel subscribers
Tracked posts907Indexed post count
Recent reach10,571Sum of recent post views
Recent posts

Recent posts

Page 8 of 76 · 907 posts

Posted Nov 2

还记得我上一条对比过 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 导致多一次值拷贝到栈上多了一大堆指令😀(然后我改了半天都降不下去,算了去做其他事情先) 计算机实在是太神奇了,我在这里找到归宿。

595 views

Posted Nov 2

saka 老师前几周分享的这篇文章 https://skoredin.pro/blog/golang/cpu-cache-friendly-go 非常有意思,我虽然日常在 pahole 输出里看到 cacheline,但对其如何影响程序运行的理解也非常模糊。 不过更有意思的是,我已经见到三位工程师在 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 基本在帮倒忙,我感觉像是在星际航行,目睹所有令人惊叹的宇宙奇观,恍惚间就化入无穷。

2,420 views

Posted Oct 29

读了很多很多很多遍报错,逐字节默念对比,心里一直冒火:,struct iov_iter_snapshot * 和 struct iov_iter_snaphost * 两个类型不是一模一样吗,怎么就不兼容了,sb clang,报的错是人能看的吗,猴子打字机🤬 二十分钟后:😭

525 views

Posted Oct 27

感到自己的工程直觉很糟糕,查一个 bug 和 GPT 头脑风暴到前列腺爆炸,不断排查了以下方向: 1. iov 在某些情况是用户态指针,比如零拷贝; 2. 特殊的 syscall 导致独特的数据形状,比如 recvfrom(..., flag=PEEKMSG); 3. 并发导致脏数据; 4. 多线程混搭读数据的现象,比如 thread1 阻塞在 readv(2) 而 thread2 不断用 recvfrom(PEEKMSG) 轮询; 5. 未知的实现细节导致入参某个字段在运行中被修改; 6. clang 过度优化编译出了错指令; 7. 内核栈的副作用,毕竟是 envoy tproxy; 没想到最终发现是我把 u64 截断成 u16,65536 (0x10000) 被截断成 0 (0x0000),被自己菜到无语😅希望能赐我一个千年智慧轮,让我把这 bug 看得清清楚楚明明白白真真切切...

564 views

Posted Oct 27

6.1.153-175.280.amzn2023.aarch64 不能 attach fentry 我rnm啊啊啊啊!!!

625 views

Posted Oct 25

在仔细读一篇技术博客,一边读一边内心戏:“错误百出”、“缺乏观察力,更缺乏想象力”、“连最基本的方法论都没有搞懂”、“完全错过了最核心的要素”、“错误的测试+错误的推论==完全不着调的结论”、“浅尝辄止,AI随机生成了一堆解释ta完全不理解是什么意思就奉为真理开始对外输出”、“太浮躁,太在意声望,却也不看看自己写的东西和猴子打字有什么区别”、“就这水平居然还卖课出书,花钱听ta胡说八道的真可怜”…… 我实在太苛刻太高傲了,我因为在私下锐评同事也是被朋友善意批评过很多次了,恐怖高压直立猿,虽然并非不苟言笑但气场高冷,有一种“你要是问我蠢问题我就要不耐烦地表演一个后空翻”的气质。(不过如果是我欣赏的人就会很热情) 这种性格甚至在越走越远,我问 gpt5: 古希腊立法者 Draco 以制定极其严苛的法律而闻名,因此出现了单词 draconian 表示政府对人民施加的法律过度严苛;有没有一个单词来表达相反的立场,即 **人民对政府施行高压监察和严苛的事后追责清算政策** 我的政治光谱已经开始朝着这种完全不正常的方向偏移了,叹气。 这样不好,这样是找不到对象的。要宽容,要躺着,在这没有答案的日子里,我需要选择忘记。

613 views

Posted Oct 25

基础软件的 bug 真是让人很受伤。 bpftrace 今年六月合并的这个 https://github.com/bpftrace/bpftrace/commit/4e2f3c7934580dfd565468d797cdc6d023075f96 引入了一个 bug,如果 btf 里的函数参数是六个,则把这个函数错误地算作没有 btf 类型记录。这导致 bpftrace v0.42+ 总是报告不存在六参数的 fentry ``` # v0.23.5 ✅ $ ./bpftrace-v0.23.5 -lv f:tcp_recvmsg_locked fentry:vmlinux:tcp_recvmsg_locked struct sock * sk struct msghdr * msg size_t len int flags struct scm_timestamping_internal * tss int * cmsg_flags int retval # v0.24.1 💊 $ bpftrace-v0.24.1 -lv f:tcp_recvmsg_locked # nothing ``` 所以我一直以为 6.1 的 tcp_recvmsg_locked 被编译器玩坏了 btf 和 ksym,比如最臭名昭著的就是 finish_task_switch 这个重要的内核调度函数经常被编译器优化为 finish_task_switch.isra.0 | finish_task_switch.isra.0.cold,然而 btf 里记录的依然是 finish_task_switch,导致 fentry 无法 attach 这个函数。 我头脑风暴了几种方案用来解决这个问题,在 tcp_recvmsg 可以 fentry 但没有锁、要手动处理并发;在 tcp_recvmsg_locked 可以用 kretprobe 但要手动管理入参;性能要仔细审查,多两次 bpf map lookup+delete 大概就会 +1% cpu usage;或者用个巧妙的无锁方案。。。。同时思考那几个“小”bugs 是否根因是我没有处理好并发导致 iovec 读崩了。。。。心智负担++ 直到我在 arm 的 6.1 内核用 main 编译的 bpftrace-arm64 发现,怎么 arm 就有 tcp_recvmsg_locked 的 fentry? 内核版本不是一样的吗? 然后我开始仔细手动检查 btf、kallsyms、tracing/available_filter_functions,发现 amd64 也应该是有的,我这才开始怀疑 bpftrace v0.24 有 bug、没能正确报告可用的 fentry。翻了一下发现三周前 Jordan 已经修了: https://github.com/bpftrace/bpftrace/pull/4701 ,目测在 bpftrace v0.24.2 就会包含这个修复。 回想起这个过程我就是无语,基础乳尖如果变得不可靠那整个技术选型、调查链路的基础都是错的,但人类就是会犯错,软件永远有 bug,工程师也不可能步步审查日常使用的基础软件是否报告的了错误的结果,只能依靠运气和工程警觉在未来某一步突然意识到“不对,这里怎么和我预期的不同,我要赶紧回头看看”,才有可能偶然纠正。 我真是又爱又恨。 BTW. bpftrace 虽然没有把 arm64 版本放入 release artifacts,但可以在 github action 里直接下载 main 的编译,乳头所示。

1,420 views

Posted Oct 22

好耶我的 Invincible 合订本到了🙋‍♀️Too slow, too weak, too easy!

560 views

Posted Oct 21

鼓起勇气看了一遍自己的演讲,发现讲得还挺好的,除了口齿不清之外 😀但应该还挺有趣的。 https://www.bilibili.com/video/BV121Wnz1ELm/ BTW 🏳️‍🌈征婚(

1,650 views

Posted Oct 21

又在吃 sockmap 的 bug 了,绝望中看到了这篇 lwn 我居然释怀地笑出来了: https://lwn.net/Articles/1038360/ https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=604326b41a6f (bpf, sockmap: convert to generic sk_msg interface) 在 2018 年合并之后的八年里有 51 个 patchsets 来改它引入的 bug,而我在 6.11 上还在遇到新 bug,在 EC2 6.1 生产环境上更是灾难,我已经放弃了这种每周两个内核 bugs 的侦探生活,连夜改 sockmap 为 tracepoint 也许还能两难自解、挽救自己岌岌可危的试用期🤬🤬 (谢谢你 Daniel Borkmann 我的老同事 😀

537 views

Posted Oct 19

半夜玩 perf 玩到脱肛心想这么好玩的东西怎么资料这么少呢,如果我能活到40岁就写一本书吧😋 计算机编程(和游戏!)实在太好玩了,就算12年过去,它们在我心灵中唤起的惊奇和敬畏依然日新月异,不断增长。

495 views

Posted Oct 18

看完《无敌少侠》S1了,全能人太性感啦,手臂比头都要粗,男主不戴难看的头套的时候也很帅。 其实我是云格斗游戏 MK1 才认识全能人 Omni-Man 的,可以欣赏一下他在 MK1 里的打斗视频,那一套从天而降的拳法实在把我看爽了: 【真人快打1:全能人vs绝对零度(天梯百强绝活)无码】 【精准空降到 07:12】 https://www.bilibili.com/video/BV16wKGzwE5J/?t=432

567 views
12•••5678910•••15•••20•••25•••30•••35•••40•••45•••50•••55•••60•••65•••70•••7576