Welcome to the Black Parade
СтатистикаDeath has many faces, I look forward to seeing this one.
- Последний пост
- 14 авг.
- Последнее чтение
- 13 авг.
- Постов за неделю
- 2
- Всего постов
- 22
- Тип
- открытый
- Язык
- английский
- В каталоге с
- 13 авг.
- 1/24сутки в ленте
- 746
- 1/48двое суток
- 854
- 1/72трое суток
- 922
Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.
Посты
видео или голосовое, без подписи
我才从前同事听说 Kubecon 明文要求三人及以上的联合演讲必须有女性: https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america/co-located-events/cfp-colocated-events/#submission-types Panel Discussion: 35 minutes of discussion amongst 3 to 5 speakers https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america/co-located-events/cfp-colocated-events/#important-notes In an effort to promote speaker equity and inclusivity, CNCF does not accept submissions with all-male panels KCD 也有类似的:https://github.com/cncf/kubernetes-community-days/blob/main/committee-resources/content-management.md Kubernetes Community Days prohibits all male panels. Please plan to have a minimum of two women speakers on a panel. 而且真的是严格执行,所以有个别 session 甚至不得不找一位背景不对口的女性 speaker …… 查了一下 LPC 其实也有: https://www.linuxfoundation.org/about/diversity-and-inclusion-at-our-events No all-male panels, all-male speaker line-ups or all-male keynote line-ups 不过 LPC 似乎没有严格执行了,我随便看了一下 LPC2024, https://lpc.events/event/18/contributions/1775/ 的三位 speakers 都是谷歌男工程师,放到 Kubecon 里就通不过了。
悲剧啊😭大意了,因为初始症状没有刀片嗓我就没有第一时间做肛拭子,还坚持去办公室投毒,问心有愧,决定翘班从现在开始🤨
手滑眼瞎型 bug 是我一生之敌,下面的 n 皇后我一次写出然后 wrong answer,肉眼观察+心灵分析+小黄鸭debug十多分钟毫无进展只能请教 LLM 才发现写错变量。别问为什么不一开始就 LLM debug,面试考察心灵😭至于为什么我还在用心灵面试,中老年再就业是这样的,人无再少年😊 class Solution: def solveNQueens(self, n: int) -> List[List[str]]: chess = [[0] * n for _ in range(n)] ans = [] def search(idx): if idx == n: o = [] for line in chess: o.append(''.join('.' if not x else 'Q' for x in line)) - chess.append(o) + ans.append(o) return for col in range(n): ok = True for i in range(idx): if chess[i][col] == 1: ok = False break offset = idx-i if offset+col < n and chess[i][offset+col] == 1: ok = False break if col-offset >=0 and chess[i][col-offset] == 1: ok = False break if ok: chess[idx][col] = 1 search(idx+1) chess[idx][col] = 0 search(0) return ans 然而真正的主题其实是游戏推荐, 塔防爱好者有福了, https://store.steampowered.com/app/355980/Dungeon_Warfare/ 好玩得不得了,我已经高潮了一个多星期了🤬 (八月更有 XCOM2 团队的战旗续作,期待❤️)
看了昨天 @ManjusakaH 转发的 simd 科普之后我才知道原来字符串搜索也可以 simd,我当时就萌生了一个大胆的想法,何不买一根 335mm Φ8mm 的金属棒 何不让 bpf 也用 simd 搜索 http header,目前的实践都是狗屎,我和同事在 6.1 ec2 上编译 kfunc 的惨痛经历仿佛还在昨天😭 任务是从巨型 HTTP/1.1 payload 里搜索你喜欢的 header,比如 Host: GET /url HTTP/1.1 User-Agent: xxx X-Forward: xxx X-Metadata: xxx [...1000+ bytes...] Host: xxx 虽然 bpf simd 是做不了的,但可以 SWAR 啊,我用 go 写一遍是这样的 const ( repeatLF = uint64(0x0a0a0a0a0a0a0a0a) ones = uint64(0x0101010101010101) highs = uint64(0x8080808080808080) ) func findHostHeader(buf []byte) int { cursor := 0 lineStart := false for cursor < len(buf) { if lineStart && cursor+5 <= len(buf) && bytes.EqualFold(buf[cursor:cursor+5], []byte("Host:")) { return cursor } lineStart = false if cursor%8 == 0 && cursor+8 <= len(buf) { word := binary.LittleEndian.Uint64(buf[cursor:]) x := word ^ repeatLF matches := (x - ones) &^ x & highs if matches != 0 { cursor += bits.TrailingZeros64(matches)/8 + 1 lineStart = true } else { cursor += 8 } continue } // Scan only to the next alignment boundary or the end. scanEnd := min((cursor+7)&^7, len(buf)) for cursor < scanEnd { if buf[cursor] == '\n' { cursor++ lineStart = true break } cursor++ } } return -1 } 虽然看起来一大坨,但实际理解并不复杂,for 循环里每次 cast 8 字节成 u64 用位运算检查这里是否有 \n,有的话说明是行末,buf[idx:idx+5] 可以用来判定 "Host:";否则不是行末,直接推进 8 字节。 这个 swar 做法虽然打不过 simd,但是两倍性能于逐字节比较还是轻轻松松的,更美妙的事它可以用 bpf 实现出来, microbench 的结果是在所有情况下都是逐字节比较的 3~4 倍,甚至比 kfunc bpf_strnstr 的性能也快 2x 了,因为 kfunc 也是逐字节的,只是少了一些 bpf_loop callback 开销而已 Scenario Bytes Byte ns/op SWAR ns/op Kfunc ns/op SWAR speedup Kfunc speedup first 64 76.0 24.0 61.0 3.17x 1.25x middle 512 522.0 144.0 267.0 3.62x 1.96x late 1536 2238.0 554.0 1018.0 4.04x 2.20x absent 2048 3745.0 923.0 1718.0 4.06x 2.18x http payload parsing 大概占用了 50% 的观测消耗,这部分性能优化 4 倍,根据 Amdahl's law,整体性能提升是 1/((1-0.5) + 0.5/4)) = 1.6 倍,如果之前是观测造成是 latency 是 +7%,这个优化可以把 latency 降到 +7%/1.6 = +4.4%,这也是符合观测结果的,嘻嘻,无敌。
下面的 go 程序能跑起来吗? package main import ( "log" "net" "os" ) func main() { ln, err := net.Listen("tcp", ":"+os.Args[1]) if err != nil { log.Fatal(err) } defer ln.Close() select {} } 有了 LLM 就跟玩游戏输秘籍似的,operation CWAL,猜谜都不 exciting 了😵 直接快进到答案,取决于 CGO_ENABLED,=0 会被判定死锁,=1 可以跑起来,原因就问 LLM 吧🤬 (发现 ps -o wchan 很好用,还有 /proc/pid/stack,procfs 真是大秘宝
我手搓的 HTTP/1.1 header 搜索居然比 bpf kfunc 还快,直达天际的好奇心,咕咕咕咕,不可思议都成了常识,拉哩噜雷啦啦啦。 事已至此,先发一张AI裸体虎杖悠仁 奥西里斯天空龙冷静一下,明天 wfh 摸鱼的时候再仔细介绍,感谢隔壁频道给我零感让我异想天开,性奋。
中午已经听到捷报,AI在人类看世界杯的时候随手证否了八十多年前的著名数学猜想😂 猜你喜欢:OpenAI 证明单位距离猜想 https://m.bilibili.com/video/BV1G7jJ6nEbV
病假当年假休了十天,在b站学习筛法证明素数的有界间距,独立战旗游戏《Tactical Breach Wizards》全成就进度68%,三场中老年异性恋地下情人故事会,不看书不学习一行代码都不写,马龙甚至还拿了男双冠军,这样的日子不多,要珍惜。
已品😭两面傩兰的上一作我完全嗤之以鼻,但这一作实在是太棒了,比马眼棒还要棒!
видео или голосовое, без подписи
众所周知,“苏俄历史上唯一一次考虑使用核武器是针对中国”——知乎 作者:牛角挂书 链接:https://www.zhihu.com/question/606136741/answer/2032231387009435041 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 1969年8月20日:苏联驻美大使多勃雷宁奉命在华盛顿紧急约见美国总统国家安全事务助理基辛格,在白宫地下室进行了通宵密谈,正式通报了苏联的完整计划。打击目标:优先摧毁中国酒泉导弹发射基地、罗布泊核试验场、青海核原料加工厂三大核设施;其次打击北京、长春、鞍山、沈阳等政治中心与军工集群城市。为了避免美国可能的反对,计划中删除了东南沿海城市,防止美国以人道主义危机的理由反对该计划。打击手段为动用远东、中亚军区部署的SS-4、SS-5中程弹道导弹,携带100万-500万吨级核弹头,实施多轮饱和打击,计划投入核弹头总量达120枚,总当量相当于2000颗广岛原子弹。 1969年8月21日:尼克松总统召开国家安全委员会绝密会议,经过通宵磋商,全票否决苏联的中立要求,明确反对对华核打击,主要原因有三条:第一,核放射性尘埃会随北半球西风带,直接覆盖日本、韩国、西太平洋区域,美国在该区域部署的25万驻军将遭受严重的、不可逆的核辐射伤害。第二,苏联人没有信用,万一他们搞假途灭虢之计,以打击中国为名,实际要打击美国核基地,等导弹飞一半再反击就来不及了,必须在苏联导弹升空的第一时间就开始反击。第三,苏联的胃口是永远不会满足的,若中国被核打击摧毁,苏联将能把全部军事力量集中到欧洲方向,北约将面临灭顶之灾。尼克松此时还是吃了没有接受中国九年义务教育的亏,否则他此时应该说一句:“夫俄,何厌之有?既东击华,又欲肆其西封,若不阙欧,将焉取之?” 1969年8月22日,美国正式答复苏联大使,反对核打击中国的计划,只要苏联对中国发射任何一枚核导弹,美国将立刻对苏联本土发动全面核报复。 1969年8月28日,考虑到中美间并无可靠的信息传递渠道,尼克松授意《华盛顿明星报》在头版头条刊登《苏联欲对中国做外科手术式核打击》的独家报道,将苏联的绝密核计划完整公之于众。报道发布后,全球舆论哗然,世界各国纷纷谴责苏联的核冒险行为,苏联陷入空前的外交孤立,勃列日涅夫暴怒,大骂美国的“出卖与愚弄”。而尼克松撇得干干净净:“众所周知,美国没有秘密嘛!” wiki 珍宝岛事件 和中国共产党新闻网 1969年中苏核危机始末 都有记录这件事,不过其中的细节是真的吗?欢迎来到本频道的不定期考古栏目😇 在 The George Washington University 的国家安全归档网站上有非常多美国政府公开的文件,The Sino-Soviet Border Conflict 正好整理了这个事件。 首先时间就对不上,美国文件里说是: - 1969-08-16 研究中国的专家 Whiting 会见基辛格,提到了中苏边境冲突和潜在的战争风险,Document 9 - 1969-08-18 KGB 负责外交事务的官员 Boris Davydov 问美国 INR (The Bureau of Intelligence and Research) 的官员,如果苏联核打击中国核设施,美国将如何应对,Document 10 - 1969-08-21 美国国务院给美国驻香港领事馆发电报,报文中明确提到 08-18 的那场会面细节和美国态度,要求大家关注苏联类似的试探提问,Document 11 - 1969-08-25 美国给 NATO 发电报,通报了中苏冲突情报,Document 12 这些事件和中国的记录“8月20号苏联驻美大使多勃雷宁奉命在华盛顿紧急约见美国总统国家安全事务助理基辛格“ 完全对不上,我肯定相信美国的记录,毕竟美国公开的电报/会议纪要的时间都是一致的。 然后是中国版本里的“1969年8月28日……《华盛顿明星报》在头版头条刊登《苏联欲对中国做外科手术式核打击》”,巧了不是,美国人最喜欢做历史报纸的数字归档了,我花了 $19.95 月订阅费之后,在 GenealogyBank.com 找到了 1969-08-28 那天的 Washington Evening Star 报纸的数字版,妈的美国佬真是牛逼,数字检索做到了 OCR 级别,总之我把 A1 和 A6 版面都上传到附件了,其中标题和内容和共产党的记录都有出入。 *《华盛顿明星报》这个名字也很迷惑,按照 wiki The Washington Star 的记录,在 1969 时它应该是叫 Evening Star;我也在归档网站按照地区 + 前后日期 + 关键词 Soviet 检索,确实只有 Evening Star 有这个报道。 来看下内容,共产党的记录是“摧毁中国酒泉导弹发射基地、罗布泊核试验场、青海……打击北京、长春、鞍山、沈阳”,然而 Evening Star 的报道是“Lanchow, Paotow, Lop Nor” 兰州,包头,罗布泊,哪来的酒泉北京长春? 至于网传“1969年8月22日,美国正式答复苏联大使……只要苏联对中国发射任何一枚核导弹,美国将立刻对苏联本土发动全面核报复”更是无稽之谈,没有任何记录支持这个说法。 事已至此,我也没有兴趣继续查下去了,中国这边的说法基本都是虚构文学,除了框架可能符合历史,但细节大量随机生成,符合我对党的刻板印象。这些数据多污染几轮 LLM,日后更加没人能从 LLM 了解到真实的历史,我感到失望又无能无力。
天雷滚滚我好怕怕 地铁看吵架我笑嘻嘻: https://lwn.net/SubscriberLink/1080162/795d3838e471262c/ The series is completely unmergeable as it stands. Not even close. code war crimes against __rmqueue_smallest() something you expect to see on a 1990's PHP website, not in core mm code 导演剪辑版: https://lwn.net/ml/all/aj9yrlB0TrlYCLlf@lucifer 评论区还有 DLC I'll stop subscribing to LWN unless you stop this mindless LLM marketing. I'm not interested in being sold these "warez." 美好😊龙腾世纪启动
惊呆了,你们金融公司用的编程语言一个比一个不正常,我在十多年前非洲学过 APL 家族的 J 语言,所以一眼认出这是 APL 家族的 Q 语言,给大家感受一下这种函数式面向数组语言是怎么写递归阶乘的: {$[x=0;1;x*.z.s[x-1]]} 数组是第一公民数据结构: q)dict:`items`sales`prices!(items;sales;prices) q)dict items | nut bolt cam cog sales | 6 8 0 3 prices| 10 20 15 20 访问 kdb+ 的 TCP server: q)h:hopen 5000 / connect to the server q)h(add;2;3) / pass the client function 'add' to the server and execute, passing 2 parameters 8 就这居然还有五位高手投简历,绝😅
抹茶发现 netfilter userspace library "libnftnl" 的 examples/nft-set-elem-add.c 跑不起来,看 syscall 也看不懂,毕竟 netlink msg 谁™能看懂? 一起痛骂 “libnftnl 是什么垃圾” 和 “你的 7.2 内核太新了,新内核全是 bug 很合理” 之后,我也好奇起来了,netlink syscall 报错溯因对我来说也是个谜,正好学习一下,而且这周工作写文档太恶心了想裸奔。 经过一堆准备工作之后: 1. 修改代码让它变成一个“启动暂停、 ctrl-c 继续运行” 的两阶段程序,延长进程生命方便我用 pid 过滤事件 2. 下载 linux-image-$(uname -r)-dbgsym 方便映射符号 3. 准备 @eBPFTalk001 的 bpfsnoop 方便用 lbr 回溯内核执行流 4. 准备 brendangregg/perf-tools 方便用 funcgraph 5. 下载 Ubuntu-hwe-6.17-6.17.0-35.35_24.04.1 源码 神秘的 Linux 内核系列又回来啦! 首先要找个合适的回溯点。直接从 __x64_sys_sendto syscall 回溯 lbr 得不到太多信息,全是 kfree_skb 之类的清理,所以找找看 netfilter netlink error 相关的 kprobe,结果还真找到一个: $ bpftrace -p $(pidof nft-set-elem-add) -e 'k:*nf*err* {printf("%s\n", probe);}' Attached 11 probes kprobe:nfnl_err_add 然后用 lbr 检查内核是如何执行到这里的: $ ./bpfsnoop -k nfnl_err_add --output-lbr --filter-pid $(pidof nft-set-elem-add) --mode entry __nla_validate_parse+0xb6 (lib/nlattr.c:655) -> __nla_parse+0x23 (lib/nlattr.c:734) __nla_parse+0x35 (lib/nlattr.c:734) -> nft_data_init+0x77 (net/netfilter/nf_tables_api.c:11894) nft_data_init+0xae (net/netfilter/nf_tables_api.c:11847) -> nft_data_init+0x115 (net/netfilter/nf_tables_api.c:11890) nft_data_init+0x11a (net/netfilter/nf_tables_api.c:11890) -> nft_data_init+0xba (net/netfilter/nf_tables_api.c:11912) nft_data_init+0xe0 (net/netfilter/nf_tables_api.c:11912) -> nft_add_set_elem+0x2be (net/netfilter/nf_tables_api.c:7380) 还有另一种思路,看整个 syscall 的 funcgraph,也能看到 nfnl_err_add 和完整的执行流 $ ./funcgraph -p $(pidof nft-set-elem-add) -m 50 __x64_sys_sendto 6) | nf_tables_newsetelem [nf_tables]() { 6) 0.634 us | nft_set_lookup_global [nf_tables](); 6) | nft_add_set_elem [nf_tables]() { 6) 0.586 us | nft_data_init [nf_tables](); 6) 1.659 us | } 6) 7.547 us | } 6) | nfnl_err_add [nfnetlink]() { 6) | __kmalloc_cache_noprof() { 6) 0.106 us | __cond_resched(); 6) 1.109 us | } 6) 1.444 us | } 两份 trace log 一结合,立刻能知道是在 nft_data_init() 里返回了 -EINVAL,然后用 lbr 跳转记录仔细跟一下源码,看到这一条跳转: nft_data_init+0xae (net/netfilter/nf_tables_api.c:11847) -> nft_data_init+0x115 (net/netfilter/nf_tables_api.c:11890) 对应的源码是 11846 if (desc->len) { 11847 if (len != desc->len) 11848 return -EINVAL; [...] 11890 return -EINVAL; [...] 看懂了吧,len != desc->len 所以 return -EINVAL,看下 desc->len 是 nft_set->klen 由 nft table 的 set type 决定的长度,比如我是用 nft add set ip t s '{ type ipv4_addr; }' 定义的 ipv4_addr type, desc->len = klen = 4;但是 nft-set-elem-add.c 里传给 netlink 的 uint16_t data 长度是 2,所以内核检查不通过,返回 -EINVAL,QED。 你以为我很高兴吗,不,我很悲伤,因为我其实一开始就用 LLM 解答万物了,把 Linux 源码 + nft-set-elem-add.c 源码 + strace 日志扔给 codex5.5 medium,它只用了三十秒就找到了问题;然后我自己再用上面这些眼花缭乱的 tracing 手段去追溯源码,用了一个小时。投降了,已皈依 LLM 神教饶我狗命🐕
cilium/ebpf 的 zero-copy ringbuf 终于合并了,测试在 64 字节消息传输时性能 x2.5,1024 字节消息性能 x5,喜,终于赶上隔壁 libbpf 和 aya-rs 😊 不过我最想吐槽的还是这期间糟糕的开源协作体验。我去年12月提交了第一版 RFC draft PR,以 florianl (点名表演)为首的维护者不断发起在我看来是 nitpicking 的 review comments,大部分都是只批评但不解决问题,偶尔提一个带方案的建设性意见也是顾头不顾尾,我立刻就用他自己的反对意见来反对他自己的方案,拉扯几轮之后我感到无聊了,就自己分叉 vendor 进我的项目了,谁爱改谁改。 我举两个例子 1. florianl 和 lmb 认为 reader 必须有并发安全,否则多个 goroutines 乱序并发执行 Read() 会导致 mmapped ringbuf offset 混乱。 这从大道理上来讲是没错,但实际工程有困难,当我按照他们的说法实现了 for rec, err := range reader.Records() { // holding reader.mutex } 之后,reader.SetDeadline() 会直接死锁;然后他们顾头不顾尾,建议 for rec, err := range reader.Records() { // reader.mutex is released reader.SetDeadline() // no dead lock } 但是这又并发不安全了,我无情指出之后他们就不说话了。 2. florianl 坚持认为零拷贝的内存在约定范围之外的使用是 bug,比如 var saved []byte reader.ReadZeroCopy(func(sample []byte, remaining int) error { saved = sample // retains reference to temporary memory? return nil }) // is saved still valid at this point? 零拷贝的内存当然只能在约定的范围内使用啊,这有什么好说的?简直莫名其妙,我直接不回复他了。 pr 放置三个月后,开源社区肥皂剧来了,另一个人开了新 pr 也做这个零拷贝 ringbuf,一看就是 100% vibe coding,没有解决任何 florianl 的任何 concerns,阿贝尔 vs 雅可比争相发表椭圆函数理论是吧。 又经历了几轮拉扯之后,ti-mo 终于亲自 pr,设计了目前的 reader.WithRelease() API,然后他作为 committer 简单收集了一下反馈就直接合并。请注意,timo 的 WithRelease 没有解决上面的两个问题: 1. 并发不安全问题,多个 goroutines 并发 lease.ReadSample() 依然会崩坏 ringbuf offset。 2. 零拷贝内存生命期问题,在 WithRelease 里把 sample 指针复制到外面的世界依然是可能的 bug。 所以最后这个功能就在 committer 的意志强行推动下,没有解决之前其他 maintainer 的 review comments,就顺利合并了,这,就是开源社区,最近一年我完全不想深度参与。 说到这里,你们以为 florianl 是什么半吊子水平专门找茬?不,他是 elastic 资深工程师,go-tc/go-conntrack 维护者,opentelemetry-ebpf-profiler 开发者,层层光环,但是协作起来我只能说难受,恶心。 可能这就是亦正亦邪让人又爱又恨的人类世界吧。
go 的 pprof 做得太好了,抬手就能查看 heap/stack/goroutine 内存消耗和调用栈,逃逸分析也有内置工具虽然很难用,更有 .gopclntab 这种开挂级别的 elf section 内置符号表,导致我一直陷入了 pprof 的淫趴无法自拔,直到这次在项目里遇到这样的情况,pprof/heap 显示堆内存 10M,goroutines 数量也很少,但是 cgroup v2 memory.current 里高达 50M,以上是这次推理海龟汤的汤面,本次推理是本格推理,请开始。 有好几个容易做错的点,第一是 pprof 查看堆内存要分别检查 gc 前后的结果,pprof/heap?gc=1,如果变化很大说明有很重的 gc 压力,可以作为一个方向去优化;第二是 cgroup 统计口径包含内核态内存,比如 bpf map,甚至 tmpdir 创建的 page cache 都会造成 cgroup memory.current++ 比如 https://github.com/bentoml/BentoML/issues/4760 ; 第三是要分清 VMA 和 RSS,这个是 OS 基础知识,但如果分不清的话,整天对着 pprof /metrics 里的 go_memstats 指标盘内存优化无疑是 bukkake。 总之走了一些弯路之后,最后发现应该首先确定用户态内存映射的 rss,直接看 /proc/$PID/status 就好了 RssAnon: 11048 kB RssFile: 37616 kB RssShmem: 0 kB Anon 包含堆栈,File 包含 elf .text,Shmem 共享内存,所以 RssFile 就有 38M 了,整天在那儿盘 pprof heap 方向就走错了。 确定了是 RssFile 就具体去统计一下 /proc/$PID/maps 里的每一段的 rss 情况,让 gpt5.5 写了一个 awk 脚本从 /proc/$PID/smaps 里抠: awk ' function flush() { if (!have) return key = name == "" ? "[anonymous]" : name size[key] += sz rss[key] += rs dirty[key] += pd } /^[0-9a-f]+-[0-9a-f]+ / { flush() have=1; sz=rs=pd=0; name="" for (i=6; i<=NF; i++) name = name (name ? " " : "") $i next } /^Size:/ { sz=$2 } /^Rss:/ { rs=$2 } /^Private_Dirty:/ { pd=$2 } END { flush() for (k in rss) printf "%10.2f MiB RSS %10.2f MiB dirty %s\n", rss[k]/1024, dirty[k]/1024, k }' /proc/$PID/smaps | sort -nr 输出结果长这样 37.15 MiB RSS 0.18 MiB dirty /home/gray/path/to/bin 8.73 MiB RSS 8.73 MiB dirty [anon: Go: heap] 2.26 MiB RSS 2.26 MiB dirty [anon: Go: immortal metadata] readelf -S --wide 一下发现 .text 就有 38M,真的是把 .text 全 page fault 进来了😀 Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 0000000000401000 001000 25c2371 00 AX 0 0 32 此处最大的谜团诞生了,因为在我本地 linux 6.1 qemu 虚拟机开发环境里,同一个二进制(通过 qemu -device virtio-9p-pci,fsdev=host_id,mount_tag=host_mount 在 host/guest 之间共享)在 host 里 file rss 38M,在 guest 只有 16M,为什么? 做了半天实验之后发现是因为文件系统不同,host binary 在 ext4 fs 上,page cache read ahead 做得很完备,每次进程执行 .text 出现 page fault 时,内核的 fault-around 机制会在 fault 地址附近对已经 read ahead 到内存的 page cache 安装最多 64K (/sys/kernel/debug/fault_around_bytes) 范围内的 PTE,如果最终需要执行的代码是“稀疏”的那就 page fault 了很多不会执行的 .text 进来;但 qemu 这里使用的是 p9 virtio fs 更像是按需读取的 fs,就不会 read ahead 大量 page 到 cache,每次 page fault 只会生产少量 rss。生产环境的容器部署是 overlay fs 能正常地 read ahead,导致也会 page fault 一大坨不会执行的 .text。 理论上来说在内存压力时内核会清理 file-backed mapping,但我实验发现单个 cgroup v2 memory.max 并不能及时触发 reclaim、依然会导致 OOM,所以另外两个解决思路,第一是纯工程地把不需要运行的代码不编译进去,用 go build -tag 什么的构建多个功能的不同镜像;第二个是 gpt 教我的,可以用 syscall madvise(MADV_DONTNEED) (https://man7.org/linux/man-pages/man2/madvise.2.html) 来强制清理 file-backed rss,然后之后进程需要再次触发 page fault 来重新映射内存。由于我这个项目的特殊姓,很多代码是在初始化时一次性运行,然后进程就进入了主循环监听事件,那些一次性执行的可以被清理掉并且不再被 page fault 回来,file-backed rss 从 38M 降到 12M。至于 /sys/kernel/debug/fault_around_bytes,虽然改小可以降低我的 file-backed rss,但会全局影响其他进程,不改。 计算机好玩吧,到底谁在说 LLM 让程序员失去乐趣🤬
群友指出上一个 post 的底层逻辑是两将军问题,已证明不可解: https://en.wikipedia.org/wiki/Two_Generals%27_Problem 我去看了最原始的资料,后来的图灵奖得主 Jim Gray 在 1978 年 “Notes on Data Base Operating Systems” P465 介绍了这个问题,并顺手给出了一个费马递降法风格的反证:假设存在这样一种可靠的协议能建立共识,取这种协议里所需通信次数最小的那个协议,考虑它最后一个通信丢包的情况:既然协议保证在这种情况依然能建立共识,说明最后这次通信是多余的,于是我们得到一个更小通信次数的协议,和假设相悖,QED。 虽然完美协商不可能,但是工程上可以把一方作为 leader 直接做决策,然后用足够多的幂等重试来推高共识概率。可以,这很最终一致性,K8s 赢。
继续《DDIA》蠕动,想起八年前让我满头大汉的一个问题。 考虑命令行 cli 发送请求给服务端 server 创建一个对象,在十年前还很流行 RESTful 的时候可能会设计 HTTP/1.1 接口 POST /api/dildos/ server 收到请求后平铺直叙地 insert 一行数据到 db,然后然后返回 200 给 cli,很简单吧,十年前本科应届生就是干这工作,北京月薪 12k ¥。 简单吗? 考虑 cli -> server 这条网络请求,它可能有一个超时设置而不是永久等待,比如 5s,超时后 cli 放弃等待,直接 close tcp conn;server 端收到 cli 的 FIN/RESET/RST_STREAM 之后,也会 close server->db 的 tcp conn 来 abort db insert,这在 Go 里是通过 Context 透传实现的,其他语言也没问题。 但是问题出在 server abort 的时候 db 状态不可知,考虑以下 race: 1. server 已经把 INSERT 发给 db 2. db 已经执行成功,并且事务已经提交 3. db 把 ok response 发回 server 4. ok response 可能已经抵达 server 的 socket receive queue 5. 但 server userspace 还没来得及调用 syscall 把 ok 读出来 6. 此时 cli 请求超时,server 观察到 cancellation 7. server 关闭 server -> db 的连接 server 视角看到的是 cli abort 了请求,server也在 db 回复之前 abort 了 db;但 db 视角是 db 成功 insert 了数据,在回复 “ok” 之后也被 server 接收到了然后四次挥手关闭连接。 (不重要的细节:如果 db response 在 server socket recv queue 里未被读就被 close(socket),内核会发送 tcp reset 而不是 fin,但这不影响讨论,mysql 等大部分 db 不会对事务完成后的 "ok" response 的 tcp reset 回滚事务,网络连接关闭没有事务回滚语义。) 此时就已经出现了数据不一致了,server 和 cli 以为他们没有创建 db 数据,但 db 实际上创建了。在真实世界场景里这很糟糕,因为 server 可能会有副作用,比如 server 是容器平台 orchestra 负责调度资源,server 回滚了实际资源,但是 db 里记账了纸面资源。如果 server 是 SDN 网络的控制平面,那会出现 web console 看到的网络配置和实际运行的网络不一致。不管是什么服务,这种状态都是不可接受的严重 bug。 十年前的 K8s apiserver 给出了一个时代级的方案:最终一致性,确实很好,这样就算 cli 请求超时也不代表没有成功创建,反正都要 watch/reconcile。但是并不是所有服务都适合最终一致性设计,在同步式 API 设计下,应该怎样正确实现 CRUD,对少年我来说一直是一个未解之谜。 我问了 GPT,它的方案是不要用 POST,而是只让用 PUT with identity PUT /api/dildos/{name} 这样就算网络超时导致状态不可知,也有一个 identity 可以用来回查状态,也可以方便实现幂等接口和重试策略。这自然不错,但是依然没有回答“如何正确实现 POST 创建接口”的问题,总有接口需要 POST 创建而不是 PUT with id 吧😀 在我进一步追问下,GPT 给了一个方案是 POST with Idempotency-Key/Operation-Id POST /api/dildos/ Idempotency-Key: 6f2a1d3e POST /api/dildos/ Operation-Id: op-123 我觉得还挺好的,已速度皈依降临派 👠 请赐我一份 AI infra (GPU, RDMA, HPC) 的工作谢谢
在看大名鼎鼎的野猪书《DDIA》,讲到多维查询 SELECT * FROM restaurants WHERE latitude > 51.49 AND latitude < 51.50 AND longitude > -0.11 AND longitude < 0.10; 书上提到的 space-filling curve 很有意思,用一个简化的例子来说明,比如 x∈[0,3], y∈[0,3] 都是整数,构成一个 4x4 网格。现在我们用一个叫做 Z-order 的算法来构造一个 z=f(x,y) 映射,把 x 在二进制下的两位 bit 分别记做 x1, x2, where x = (x1<<1) | x2, 同理 y = (y1<<1) | y2,然后构造 z = (x1<<3) | (y1<<2) | (x2<<1) | y2,得到一个如下的映射表格 y=3 5 7 13 15 y=2 4 6 12 14 y=1 1 3 9 11 y=0 0 2 8 10 x=0 1 2 3 显然这是一个双射,我们反过来建立映射 0 -> (0,0), 1 -> (0,1) ..., 然后对这个新的 z 建立索引 (e.g. B-tree) 做高性能查询。 熟悉古典集合论的已经看出这是整数集 Z 和二维整数网格 Z x Z 等势的一种构造,熟悉皮亚诺曲线的应该也能看出这是一种空间填充曲线。不过和康托尔对角线法不同,这种构造有很好的局部性质,我们仔细看上面的映射表格,0->1->2->3,4->5->6->7 ……分别是一个在 2x2 方格内的 Z 形曲线,然后把 [0,3]/[4,7]/……分别看成一个整体,发现 [0,3]->[4,7]->[8,11]->[12,15] 也是一个 Z 形,这是一个分形 Z,所以叫做 Z-curve。 说它有很好的局部性是因为,假设现在的二维查询是 WHERE x BETWEEN 2 AND 3 AND y BETWEEN 0 AND 1 这直接映射到了一段连续的查询,在 B-tree 里的性能很好。 WHERE z BETWEEN 8 AND 11 不过一个查询可能跨越多个方格,比如 WHERE x BETWEEN 1 AND 2 AND y BETWEEN 1 AND 2 这时候 z 映射是 {3,6,9,12} 就不连续了,但在一个大规模网格上,我们相信一个二维 xy 范围查询可以被分解为 z 的多段范围查询,这里的 quadtree 分解算法和我之前做端口规则非常相似,当时我要检查端口是否在 [5, 11] 范围内,我把区间分解为三个前缀树方便 BPF_MAP_TYPE_LPM_TRIE 使用: 0101 -> [5,5] 011* -> [6,7] 10** -> [8,11] 二维区间也可以类似地分解为若干个完整覆盖的方块节点,最终转为几个连续的范围查询。 DDIA久负盛名,我一直听说是“做数据库必读”,可惜我不使用关系型数据很多年了就一直没看,直到最近觉得还是应该文体两开花,就一边玩龙腾世纪一边蠕动着看书,没想到力透纸背,看得我口干舌燥,前后夹击,彻底沉迷在这种淫乱生活中。