2026-07-09 23:01:00
自从自个儿琢磨出 locate cost 之后便开始关注这方面问题。最近看到两篇喷 harness 问题的
第一个是 Can Bölük https://blog.can.ac/2026/02/12/the-harness-problem/ 今年2月的时候发现:
Codex uses
apply_patch: It takes a string as input, which is essentially an OpenAI-flavored diff, and instead of relying on a structured schema, the harness just expects this blob to follow a strict set of rules
Claude Code (and most others) usestr_replace: find the exact old text, swap in the new text. Very simple to think about. But the model must reproduce every character perfectly, including whitespace and indentation.
Cursor trained a separate neural network: a fine-tuned 70B model whose entire job is to take a draft edit and merge it into the file correctly
如果你在 Codex 用别的模型
Grok 4’s patch failure rate in my benchmark was 50.7%, GLM-4.7’s was 46.2%.
Aider’s own benchmarks show that format choice alone swung GPT-4 Turbo from 26% to 59%, but GPT-3.5 scored only 19% with the same format because it couldn’t reliably produce valid diffs.
The Diff-XYZ benchmark from JetBrains confirmed it systematically: no single edit format dominates across models and use cases. EDIT-Bench found that only one model achieves over 60% pass@1 on realistic editing tasks.
懒得看原文的我直接说结论:大家都在争论哪个模型编程更强,但很多模型都知道要改什么,失败其实发生在具体改哪里。
他做了个实验,同样的 16 个模型,只换编辑这个 tool call,改成他自己发明的 hashline,给每行内容打一个短哈希做锚点,Grok Code Fast 1 从 6.7% 直接跳到 68.3%。
Can Bölük 这个老哥非常生猛,2021年有篇博客讲他在Intel CPU 发现一个指令可以序列化/反序列化打印所有 x86 指令集。微码立功了!
他这个 hashline 也很巧思,我也是最近才琢磨明白。你仔细想就会有个疑问,为啥不直接用 行号?
有个相关的问题一直困扰我。我经常让 Gemini 去搜我博客,我博客网址都是类似 stderr-XX 其中 XX 是数字,然后 Gemini 经常把别的文章内容给我总结批判一番。我得到的结论是 LLM 不识数。
后来在学 RAG embedding vs BM25 的时候突然顿悟了,tmd 这个基于 基于语义空间的相似度匹配 有利有弊。好处是比如你检索 汽车,它能联想到 车辆,无需 FTS 那里你自己要维护一套分词近义词表。坏处是行号 223 233 它觉得很「近似」直接搞混 😂
扯远了。
然后是前几天 Armin Ronacher 的 https://lucumr.pocoo.org/2026/7/4/better-models-worse-tools/
这老哥是 Flask/Werkzeug/Jinja2作者,现在主要在撸 Pi 这个agent(值得一题的是上面的老哥在撸 oh-my-pi 这个 fork)
他发现 Opus 4.8 和 Sonnet 5 在非 Claude Code 的 harness(比如他自己的 Pi 项目)里调用嵌套 edits[] 数组时会莫名其妙地塞进一堆乱七八糟的 <antml:function_calls> 这种内部控制字符。老的模型反而没这个毛病。他推测是 A\ 家训新模型强耦合了 Claude Code。逆向发现 Claude Code 客户端对格式错误极其宽容,有一整套别名映射、Unicode 修复、静默过滤多余字段的逻辑。结果就是模型在RL中适应了格式差不多就行,反正harness 会兜底。
看完这两篇我觉得印证了我前面 locate cost 一问的所有猜想。AI 指出,真正可能的机制更朴素:
RL 训练信号里,整段重写 往往比 精确定位再小改 更容易拿到奖励。重写不会因为空白符不匹配而报错。这跟第二篇里 Armin 讲的模型在宽容的 harness 里学会了偷懒,是同一个因果链条,不需要引入纹理/结构的形而上区分也能解释。
也就是说,LLM上课答题只给答案分,不给过程分,导致背题偷懒 🤣 这个方向在研究界是有名字的,叫 process supervision / process reward model,过程奖励模型。OpenAI 那篇《Let's Verify Step by Step》基本就是在数学推理场景做这件事。
但这条路有两个真实的代价,其一是 过程标注比结果标注贵得多;其二 如果训练时的 harness 比部署时的 harness 更宽容,学出来的好过程标准本身就是错的。
所以又回到一个老生常谈的话题。各大模型厂家都在推出自己的 CLI。观察「过程」比最终结果更值钱!
我让AI去 fact check了下。果然
数据使用文档里明确写着两条完全独立的通道:
DISABLE_TELEMETRY 一条完全独立的遥测通道,文档原话是:Claude Code 会从用户的机器连接到 Anthropic,记录延迟、可靠性、使用模式这类运营指标。这类日志不包含任何代码或文件路径。关掉这条通道要单独操作,跟训练开关是两个开关、两套机制、两份文档。 设置页面里直接摆着两个并列开关
也就是说厂商自己都承认这是两套独立治理的东西——只是大多数用户可能只会想起关第一个开关。
官方文档列出了它 OTel 遥测会上报的事件类型,其中包括
codex.tool_decision 工具调用是被批准还是拒绝,以及这个决定来自配置还是用户codex.tool_result 耗时、是否成功、外加一段输出片段codex.user_prompt 默认只记录长度、内容会被打码听起来很克制,但 工具决策 + 输出片段 + 时长 这几项拼起来 就是前面说那种 过程信号,不是代码本身。精确刻画了模型在 harness 里怎么试错、被拒了多少次、跑了多久。这条 OTel 通道是靠单独的 config.toml 开关控制的,跟 ChatGPT 账号层面的训练数据开关是两件事。
方向比较模糊,它把训练相关的退出开关本身命名为 Enable Telemetry,把两件事的名字焊在一起。
Google Groups 的讨论帖里用户在问这个开关到底关不关得掉训练,官方也没给出干脆的回答。
所以接下去的推论就很简单。利用公开语料能训练出 2025年级别的sota llm。但是往后就看各家谁能拿到更多的轨迹数据了。无论靠CLI / app 装机量,还是买数据,偷数据,各显神通。其中装机量/DAU几乎正比于以后的智力天花板。所谓的 trillion tokens models 估计就是从各种日志里来的(而不是人类语料)
继续推演下去,有意思的一点是,可预见的将来,AI 的智力增长几乎全来自于coding
因为 coding 有个编译器师爷能把关,保证产出可验证!
别的什么 具身 世界模型,我觉得难了 😆
还有一个考虑的,装机量看 2C,各行业应用 2B 也很重要。比如design类的。这种“轨迹” 如何收集改进也很讲究。
但是design想了下又挺主观的。不过可以降低一些看上去很笨的地方。
甚至如果从公平的角度来说,AI厂家,除了按成本收费之外,还应该给高价值数据返钱才对。不是之前有报道说Anthropic 和 OpenAI 都签了七位数金额的 RL 环境和人类专家数据合同,预计投入还要再涨 3-5 倍。就是拿来训练“过程”的吧。
今晚娃又沉迷pad,我给他pad锁了 😆
为啥说起这个呢,我一直让娃坚持写「语音日记」
但是孩子长大了,他写得越来越不耐烦了,而且我苦恼作文没有批改,所以这个日记习惯实际上成了低水平重复。
其实把复制粘贴到deepseek,提示词 “以XX年级的标准点评改进下这篇” 就能搞定,奈何我家娃太懒。
所以我一直想给娃弄一个 作文训练 app。本来想不就是AI一问一答批改么。
但是想到 locate cost 突然觉得有点难。。。。。甚至比AI coding 还难。。
代码为什么定位相对容易。 哪怕 str_replace 因为空白符不匹配而报错,它要定位的目标本身是离散、有边界的——一行代码、一个函数,有语法(AST)天然把文档切成可寻址的单元。
或者 hashline,行号就每句话一个稳定锚点,把找位置从模糊的文本匹配变成精确的 ID 查找。这招完全可以照搬到「改病句」场景。做个 diff 也容易
但写作的问题,往往根本不是一个可以圈起来的line或者span,而是句子之间关系的性质。 “这段论证缺乏内在逻辑”,“这句话和上一句衔接生硬”,“全文的语气从第二段开始飘了”——这些反馈即便你精确指出“第3段第2句”,真正需要改的可能是第1句、连接词,或者整段重组。
代码的 bug 通常局限在一个可编辑单元里,作文的"bug"经常是分布式的、关系性的。定位到具体文字之后,改哪、怎么改这一步反而更模糊,比代码多绕一层
AI说,写作其实分两个层次
怎么切入呢?
后者也有一些实践,比如借鉴 AP 阅卷没,用锚定范文(anchor papers)做少样本参照。而不是让模型凭空判断“这篇好不好”
也可以做高亮 + 提问式 而非直接改。比如第 2 段第 3 句话里,“非常开心”这个词能不能换一个更具体的?比如描述一下你当时的表情或动作?
甚至可以 示例驱动:AI 给出 改前 / 改后 小对比,只改 1-2 处;然后 多轮对话,孩子自己决定要改哪里,AI 只辅助,而不是 AI 主导大改。
要么就局部训练,针对常见作文类型(记事、写景、议论),开头、过渡、结尾分别训练
这样看来,写作文直接给娃一张无限大的白纸并不好。参考 Notion 的 Block 概念,强迫孩子在输入时就把“骨架”和“血肉”分开。比如,第一步只允许输入 3 个论点(形式);第二步再针对每个论点去填充素材(质料)。通过产品机制,人为制造出“伪 AST(语法树)”。
或者阻断直接生成,只给“反向约束”。不输出完整的句子让孩子抄,扮演“刁钻的苏格拉底”。比如,当孩子写“今天我很开心”,AI 的反馈不应该是“你可以改成:今天我心花怒放”,而应该是抛出环境约束(Harness):“你当时手里拿了什么东西?你的心跳有多快?”——逼迫孩子自己去完成“从潜能到现实”的推导。
当然,做好 diff 和版本控制是基础。记录孩子打磨一句话的过程。让孩子直观看到词汇的微调是如何让语义的边界越来越清晰的。
哎,这么一拆解又有点思路了,但比预计的感觉麻烦得多啊。不过语文教育有大问题啊。明明是工程上可以细化训练的(虽然很难,过去没AI需要大量人工精力)。上课根本不讲。全靠孩子天生悟性。以上种种,今年4月才喷过一篇《语文学习和考试》
作文从小学一上来就300 字 500字,其实真应该「刻意练习」的是语言 primitive。什么 铺垫、呼应、留白、对比、节奏、悬念、感官、动作、心理、对话等等,都上手了,然后再各种变化,组合。
学编程也是从少量 keywords ,赋值语句,条件,循环这样一步一步来的嘛。
过去没太好的语文教学条件,归根结底因为一个班上50个娃只有一个语文老师。
但是现在有LLM了。
2026-07-09 11:02:00
一个爆火的讨论
云风 @cloudwu 2026-06-29
微信的开发人员根本就不懂该怎么储存数据。这种聊天软件,文本和媒体文件分开存,文本根本就不应该保存在什么数据库(sqlite)里, 一个对话一个文本文件追加就可以了。需要搜索的时候 grep 一下性能完全符合需求。一个对话能有多少文本?一秒一个字 24 小时不间断,一年也就 30M 个字。
网上的争论都是猜测,我呢,决定让 opus 跑一局。
首先让AI去搜微信聊录表结构
微信(Android)聊天记录存储在加密 SQLite 数据库
EnMicroMsg.db中,使用 SQLCipher(AES-256-CBC,PBKDF2 256000 轮派生密钥)。核心message表:
CREATE TABLE message (
msgId INTEGER PRIMARY KEY, -- 本地自增 ID
msgSvrId INTEGER, -- 服务器消息 ID
type INTEGER, -- 1=文字, 3=图片, 34=语音, 43=视频
isSend INTEGER, -- 0=接收, 1=发送
createTime INTEGER, -- Unix 时间戳
talker TEXT, -- wxid 或群 chatroom ID
content TEXT, -- 消息正文
imgPath TEXT -- 附件路径
);
微信支付、服务器、数据库、会议、周末
| 分类 | 方案 | 思路 |
|---|---|---|
| 传统文本 | grep (BSD) | 最朴素的逐字节匹配 |
| SIMD文本 | ripgrep | AVX2/NEON 并行 + 多线程 |
| 零拷贝 | mmap 直接搜索 | OS page cache + Python bytes.find |
| 压缩文本 | zstd 流式解压搜索 | 省空间,边解压边搜 |
| 索引 | 倒排索引 (2-gram) | 搜索引擎思路,内存索引 |
| 索引 | Bloom Filter 分块 | 概率型预过滤 |
| RDBMS | SQLite LIKE | 微信的实际方案(去掉加密) |
| RDBMS | SQLite mmap 模式 | mmap I/O 加速 |
| RDBMS FTS | SQLite FTS5 (trigram) | 全文搜索引擎 |
| 列式DB | DuckDB contains() | OLAP 列式扫描 |
| 列式DB | DuckDB FTS | DuckDB 的全文搜索扩展 |
| 列式文件 | Parquet(zstd) + DuckDB | 列式文件直接查询 |
| DataFrame | Polars lazy scan | Rust实现的极速 DataFrame |
| DataFrame | Polars in-memory | 全量载入内存 |
| 并行文本 | ripgrep 多文件并行 | 分块文件 + rg 多线程 |
| # | 方案 | 微信支付 | 服务器 | 数据库 | 会议 | 周末 | 平均 |
|---|---|---|---|---|---|---|---|
| 1 | SQLite FTS5 (trigram) | 0.65 | 0.46 | 0.37 | ❌² | ❌² | 0.31¹ |
| 2 | Polars lazy scan | 3.51 | 2.56 | 2.43 | 2.49 | 2.47 | 2.69 |
| 3 | 倒排索引 (2-gram) | 2.71 | 3.26 | 2.78 | 3.24 | 2.37 | 2.87 |
| 4 | Polars in-memory | 2.67 | 3.61 | 2.98 | 3.21 | 3.21 | 3.13 |
| 5 | DuckDB contains() | 3.72 | 3.23 | 3.18 | 3.91 | 4.41 | 3.69 |
| 6 | Parquet + DuckDB | 4.63 | 4.37 | 4.55 | 4.88 | 4.84 | 4.65 |
| 7 | ripgrep 多文件并行 | 10.65 | 10.26 | 9.48 | 10.32 | 9.05 | 9.95 |
| 8 | DuckDB FTS (BM25) | 12.53 | 11.77 | 12.85 | 11.82 | 12.78 | 12.35³ |
| 9 | ripgrep (SIMD, 单文件) | 13.24 | 13.77 | 13.40 | 14.14 | 13.10 | 13.53 |
| 10 | mmap 直接搜索 | 17.00 | 22.62 | 22.86 | 31.36 | 30.52 | 24.87 |
| 11 | Bloom Filter + 扫描 | 25.34 | 25.25 | 25.06 | 26.68 | 27.01 | 25.87 |
| 12 | SQLite mmap LIKE | 37.45 | 37.45 | 37.92 | 37.83 | 37.81 | 37.69 |
| 13 | SQLite LIKE | 43.13 | 41.40 | 41.50 | 41.51 | 41.46 | 41.80 |
| 14 | grep (BSD) | 139.10 | 136.86 | 141.42 | 122.54 | 124.16 | 132.82 |
| 15 | zstd 流式解压搜索 | 161.32 | 164.22 | 164.56 | 170.23 | 170.73 | 166.21 |
¹ FTS5 只对 ≥3字符 的关键词有效,取3个有效关键词平均
² trigram tokenizer 无法匹配 2 字符的中文词
³ DuckDB FTS 默认 tokenizer 不支持中文,返回 0 结果(延迟仍可参考)
1. █ 0.31ms SQLite FTS5
2. █ 2.69ms Polars lazy
3. █ 2.87ms 倒排索引
4. █ 3.13ms Polars in-mem
5. █ 3.69ms DuckDB contains
6. ██ 4.65ms Parquet+DuckDB
7. ████ 9.95ms rg多文件并行
8. ██████ 12.35ms DuckDB FTS
9. ██████ 13.53ms ripgrep (SIMD
10. ████████████ 24.87ms mmap 直接搜索
11. ████████████ 25.87ms Bloom+扫描
12. ██████████████████ 37.69ms SQLite mmap LIKE
13. ████████████████████ 41.80ms SQLite LIKE
14. ████████████████████████████████████████████████████████████ 132.82ms grep (BSD
15. ████████████████████████████████████████████████████████████ 166.21ms zstd流式解压
| 方案 | 延迟 (ms) | 倍率(vs grep) |
|---|---|---|
| SQLite indexed | 2.80 | 76x |
| DuckDB | 4.57 | 47x |
| Polars in-memory | 7.02 | 30x |
| ripgrep pipe | 20.47 | 10x |
| grep pipe | 213.90 | 1x |
| 格式 | 大小 | vs TSV | 说明 |
|---|---|---|---|
| Parquet (zstd) | 8.3 MB | 0.18x | 列式 + 字典编码 + 压缩 |
| zstd 压缩 TSV | 12.7 MB | 0.27x | 纯压缩 |
| DuckDB + FTS | 26.0 MB | 0.55x | 含全文索引 |
| TSV 纯文本 | 47.0 MB | 1.00x | 基线 |
| SQLite | 69.2 MB | 1.47x | B-tree 开销 |
| SQLite + FTS5 | 116.8 MB | 2.49x | trigram 索引翻倍 |
SQLite FTS5 (trigram)
- 原理:对 content 字段的每个 3 字符子串建倒排索引
- 优点:查询极快(0.3-0.6ms),无需额外依赖
- 缺点:索引体积翻倍(+68MB);trigram 无法匹配 ≤2 字符的关键词
- 适用:搜索词通常 ≥3 字符的场景
Polars (lazy/in-memory)
- 原理:Rust 实现的 DataFrame 引擎,列式内存布局 + SIMD 字符串匹配
- 优点:Parquet 文件仅 8.3MB(最小!),查询 2-3ms,复合查询也快(7ms)
- 缺点:需要加载到内存;Python 库依赖
- 杀手锏:8MB 的 Parquet 文件 + 3ms 搜索延迟,这是存储效率和速度的最佳平衡点
倒排索引 (2-gram, 内存)
- 原理:搜索引擎最经典的思路,对所有 2-gram 建 posting list
- 优点:构建仅 0.78s,查询 2.9ms,支持任意长度关键词
- 缺点:纯内存(需要序列化/加载),索引构建需要全量遍历
- 适用:append-only 数据可以增量更新索引
DuckDB contains()
- 原理:列式存储,content 列连续存放,CPU cache 友好 + SIMD 扫描
- 优点:无需专门索引即可 3.7ms;复合查询也仅 4.6ms;文件仅 26MB
- 缺点:需要 DuckDB 运行时
- 杀手锏:不建任何索引,纯靠列式布局就比 SQLite LIKE 快 11 倍
Parquet 文件 + DuckDB 零拷贝查询
- 原理:Parquet 本身就是列式格式,DuckDB 可以直接查询不需导入
- 优点:文件仅 8.3MB,不需要数据库进程,查询 4.6ms
- 缺点:每次查询需要启动 DuckDB 连接
- 杀手锏:一个 8MB 的文件就是完整的"数据库",任何语言都能读
ripgrep 多文件并行
- 原理:把消息分块成多个文件,ripgrep 的 work-stealing 线程池并行搜索
- 优点:比单文件 ripgrep 快 ~35%(10ms vs 13.5ms)
- 缺点:文件管理复杂
- 适用:数据天然按时间分文件存储的场景
ripgrep (SIMD, 单文件)
- 原理:AVX2/NEON 每周期处理 16-32 字节,多线程(对单文件仍用单线程)
- 优点:零配置,即装即用
- 缺点:对单文件只能单线程
Bloom Filter 分块预过滤
- 问题:中文常用 2-gram 只有 ~690 种,每个块都包含所有 n-gram,过滤率为 0
- 结论:对高频 n-gram 的数据集完全无效,白费构建时间
zstd 流式解压搜索
- 问题:Python 解压+搜索 166ms,比不压缩的 grep 还慢
- 结论:CPU 密集的解压抵消了 I/O 节省。如果数据在 SSD 上,不如直接读原文
- 可能有用的场景:数据在网络存储/HDD 上,I/O 是瓶颈时
DuckDB FTS (BM25)
- 问题:默认 tokenizer(类似 ICU word boundary)不支持中文
- 结论:需要自定义 tokenizer 或等 DuckDB 支持 trigram/CJK
mmap 直接搜索
- 表现:24.87ms,比 ripgrep 慢 2 倍
- 原因:Python 的 mmap.find() 是朴素搜索,没有 SIMD 优化
- 如果用 C/Rust 实现的 mmap + SIMD,预期接近 ripgrep 水平
| 优先级 | 方案 | 搜索延迟 | 存储 | 理由 |
|---|---|---|---|---|
| 🥇 | Parquet + Polars/DuckDB | 3-5ms | 8 MB | 存储最小、搜索极快、格式通用、append 友好 |
| 🥈 | DuckDB 数据库 | 3.7ms | 26 MB | 单文件数据库、列式、SQL 查询、FTS 索引可选 |
| 🥉 | SQLite + FTS5 | 0.3ms | 117 MB | 最快搜索(有索引)、但索引太大 |
| 4 | ripgrep + 分块 TSV | 10ms | 47 MB | 最简单、无依赖、人类可读 |
# 方案 A#: ripgrep 搜纯文本 (~10ms)
rg "微信支付" messages.tsv
## 方案 B: DuckDB 直接查 Parquet (~5ms)
duckdb -c "SELECT * FROM read_parquet('messages.parquet') WHERE contains(content, '微信支付')"
## 方案 C: DuckDB 复合查询 (~5ms)
duckdb -c "SELECT * FROM read_parquet('messages.parquet')
WHERE talker='wxid_xxx' AND createTime > 1672531200
AND contains(content, '会议')"
聊天记录是 append-only 数据 → 最适合列式存储 (Parquet/DuckDB)
- 不需要 RDBMS 的事务/锁/B-tree 开销
- 列式布局让
content列连续存放,CPU cache 极度友好- zstd 字典压缩对重复模式多的聊天文本压缩率惊人(82%)
- 追加写入只需要 append 新的 row group
微信搜索慢的根因:不是 SQLite vs grep 的问题,而是 SQLCipher 加密的 PBKDF2 256000 轮 + 行式存储对全文扫描不友好。如果用 Parquet + 硬件 AES,可以同时做到加密和极速搜索。
messages.parquet (8MB, zstd压缩, 硬件AES加密)
→ DuckDB/Polars 直接查询 (3-5ms)
→ 可选: 内存倒排索引 for < 1ms 搜索
→ 追加: 新消息 append 到 staging 文件, 定期 merge
→ 导出: parquet 是开放格式, 任何语言/工具可读
pip install duckdb polars zstandard cryptography
python3 benchmark.py 500000 # 基础测试 (SQLite/grep)
python3 benchmark_v2.py 500000 # 扩展测试 (DuckDB/Polars/mmap/...)
源码放在 https://github.com/est/snippets/tree/master/grep_vs_sqlite 。
模拟聊天记录有500,000行 一个 50MB 的 .tsv 太大了。自己让AI根据文章和最上面表结构生成一份吧。
被Polars的性能震惊了。说实话如果换我肯定不会选sqlite。聊天记录又不会改。ACID的 OLTP 明显浪费
2026-07-08 16:05:00
有AI就是好使,搁以前自己得盯半天也看不明白。。。
curl -NT. 在连接一个持续推送数据的 streaming 服务器时,会产生一个 紧密的 pause/unpause 循环,导致 100% CPU。
-T.(注意是点号,不是减号)将 stdin 设为非阻塞模式,然后依赖 EAGAIN + CURL_READFUNC_PAUSE 机制来避免阻塞。核心循环发生在三个回调之间:
第一步 — 上传尝试失败(src/tool_cb_rea.c):
{
rc = read(per->infd, buffer, sz * nmemb);
if(rc < 0) {
if(errno == EAGAIN) {
errno = 0;
config->readbusy = TRUE;
return CURL_READFUNC_PAUSE;
}
/* since size_t is unsigned we cannot return negative values fine */
rc = 0;
}
}
stdin 没数据 → read() 返回 EAGAIN → 设 readbusy=TRUE → 暂停上传。
第二步 — 下载数据到达立刻解除暂停(src/tool_cb_wrt.c):
if(config->readbusy) {
config->readbusy = FALSE;
curl_easy_pause(per->curl, CURLPAUSE_CONT);
}
streaming 服务器持续发送数据 → tool_write_cb 被不断调用 → 每次都无等待地立刻解除上传暂停。
第三步 — 进度回调只有 1ms 退避(src/tool_cb_rea.c):
if(config->readbusy) {
if(ulprev == ulnow) {
#ifndef _WIN32
waitfd(1, per->infd);
#else
/* sleep */
curlx_wait_ms(1);
#endif
}
config->readbusy = FALSE;
curl_easy_pause(per->curl, CURLPAUSE_CONT);
}
tool_readbusy_cb 有 1ms 的 waitfd 退避,但被 tool_write_cb 的无等待解除抢先了。
完整的死循环(每次 curl_multi_perform 内):
在 lib/transfer.c 的 Curl_sendrecv 中,处理顺序是先下载后上传:
if(k->keepon & KEEP_RECV) {
result = sendrecv_dl(data, k);
if(result || data->req.done)
goto out;
}
/* If we still have writing to do, we check if we have a writable socket. */
if(Curl_req_want_send(data)) {
result = sendrecv_ul(data);
if(result)
goto out;
}
所以单次迭代内:
1. sendrecv_dl → 读到 streaming 数据 → tool_write_cb → 看到 readbusy=TRUE → 立刻解除暂停(零等待)
2. sendrecv_ul → tool_read_cb → stdin EAGAIN → 又暂停,readbusy=TRUE
3. 回到 curl_multi_poll → 下载 socket 有数据 → 立刻返回(不会 sleep)
4. 重复 1-3
而 lib/easy.c 的主循环:
while(!done && !mresult) {
int still_running = 0;
mresult = curl_multi_poll(multi, NULL, 0, 1000, NULL);
if(!mresult)
mresult = curl_multi_perform(multi, &still_running);
curl_multi_poll 的 1000ms 超时形同虚设——因为下载 socket 始终可读,poll() 立刻返回。
结果:每秒跑上千次这个循环,每次包含多个系统调用(poll、read、write),CPU 直接打满。
stdin 没有被加入 curl_multi_poll 的 pollset。只有网络 socket 参与了 poll。所以 curl 无法高效地"同时等待 stdin 和网络数据",只能靠 tool_write_cb 里的盲目 unpause 来弥补,在 streaming 场景下退化成忙等。
如果你不需要从 stdin 上传数据(只是想看 streaming 响应),不要用 -T.:
# 正确的方式:只看 streaming 输出
curl -N https://streaming-server/events
# 或者重定向 stdin 为空
curl -NT. https://streaming-server/events < /dev/null
< /dev/null 会让 read() 返回 0(EOF)而不是 EAGAIN,不会进入 pause/unpause 循环。
如果确实需要双向 streaming(边上传 stdin 边接收),目前没有完美的用户侧 workaround。可以考虑:
# 用管道加一层缓冲,减轻空转(效果有限)
cat | curl -NT. https://streaming-server/events
如果要在源码层面修复,最直接的方案是在 tool_write_cb 中去掉盲目 unpause,改为只在 tool_readbusy_cb 中带退避地 unpause,并增大退避时间:
// tool_cb_wrt.c — 去掉这段,或加条件限制
// 不再从 write callback 无脑解除上传暂停
if(config->readbusy) {
config->readbusy = FALSE;
curl_easy_pause(per->curl, CURLPAUSE_CONT); // 删除或改为有条件触发
}
// tool_cb_rea.c — 增大退避时间
if(config->readbusy) {
if(ulprev == ulnow) {
waitfd(100, per->infd); // 从 1ms 提高到 100ms
}
config->readbusy = FALSE;
curl_easy_pause(per->curl, CURLPAUSE_CONT);
}
更彻底的方案是把 stdin fd 加入 curl_multi_poll 的 extra fds,但这需要改 curl_multi_poll 的调用方式,涉及面更大。
2026-07-06 15:51:00
无聊刷博客,《AI如何导致和修复了我的失眠问题》 这老哥真猛,通过统计规律发现自己 失眠 的罪魁祸首。(不剧透。感兴趣点开阅读)
我自己也写过 快速入睡的技巧和原理 以及 强行入睡方法 v2.0 其实我都忘记这个 2.0 方法了。都不知道自己当时怎么想到这个办法的,原来自己写的东西也能常看常新(老登健忘症😂),所以还是要多写,多记录
本文章的讨论都是基于这个 2.0方法的,接着看之前请务必点开 2.0 那个链接,不长,一会儿就看完。
然后我就无聊让AI 评价一下这个 2.0 方法是不是真的,然后AI说真有学者在搞类似的,关键词 :
然后我去搜了下
TikTok和Instagram上爆火的“认知洗牌法”,火到连医生都开始推荐。选一个随机的单词(比如 cake,蛋糕),专注于这个词的第一个字母(C),然后列出一串以这个字母开头的词:cat(猫)、carrot(胡萝卜)、calendar(日历)等等,一边列举,一边在脑中想象这些词的画面。当你准备好了,就转到下一个字母(A),重复上述过程,继续进行下去(K、E),直到你睡着或者想换一个新词为止
嗯,和我的方法居然殊途同归,只是更加麻烦,需要调用大脑的语言区。
但是自媒体这个标题让我产生了兴趣 《别再数羊了》,我恰好周末刷了 西藏那曲拉姆 的视频 藏族人家里几十上百头牦牛,如何识别是不是自己家的?
然后突然发现一个被大多数人忽略的惊人的事实:放牧人的白天是极度无聊和空虚的,以至于他/她们能辨识自己每一头羊的特征、性别,甚至给每头羊起个名字。视频说牦牛和人一样,每一头都有独一无二的毛色、长相、形态等。
脸上有点黑?叫小黑;黑白相间的?叫花脸。按脾气起名字,暴躁哥,温顺妹;还有谁和谁喜欢一起吃草,小牛的母牛妈妈是谁等等。
所以这两件事就串起来了。「数羊」这事儿一定是牧区的人发明出来的,比如英国乡下,但是城里人哪里知道这些细节啊。
牧民晚上躺床上,没事干,说不定就给自己牛羊编故事造剧情啊。而且关键词是「数」,你不能陷入一个逻辑推理细节,必须不停地轮换,把羊变成高频切换的具象个体(有名字、有外形、有性格),才能保证大脑疲惫然后入睡。
AI总结:
对于古代或乡村的牧羊人来说,夜晚盘点羊群是一天结束时最让人安心的闭环。他们脑海中的羊,确实是毛发质感、体态特征各异的具象实体。
现代城市人剥离了这种生活语境,把一幅丰富的田园3D渲染图,降维成了枯燥的Excel递增表格,自然也就失去了助眠的神奇功效。
我觉得AI真有点东西的。清点生产资料也是被忽略的一环。如果我睡前都能盘点自己该做的事儿都做了,羊吃饱了,明天会更好,没啥落下的,那我肯定也睡得安稳啊。
但是现代人难就难在很多事是跨很多天的,入睡是非常不情愿的。如果有精力很多人甚至愿意熬夜。
都怪爱迪生,本来以为发明电灯泡给人类漫长的黑夜带来光明,没想到人类却用这玩意来加班和难眠!!😤 😤 😤 如果太阳下山就睡觉,就算失眠4小时你到23点也睡着了 😂
2026-07-02 16:07:00
看到一篇雄文《明末士大夫为什么毫无气节纷纷变节投降满清?》大受启发,想看原文的可以点开链接,下面是精简和摘录:
因为程朱理学在理论存面存在漏洞,被鞑子无意之间利用了,事实上大部分的鞑子统治者在这方面,也都是知其然而不知所以然。
不否认明末也有很多了不起的仁人志士,但如果你对中国历史有些疑惑,觉得似乎宋朝之后中国就有点不一样了,那恭喜你,你的直觉是对的。由于元代留下的遗毒过甚,明朝没能拨乱反正,元明清三代,所有的皇帝和“儒家士大夫”,都是失礼而不自知之人。元明清三代的所谓“礼法”,放在先秦两汉的大学者们面前,诸子百家不管哪家,都一眼就能看出来全是假礼。如果墨子这个儒家最大的反对者看见了,估计更是嘲笑孔子能笑的棺材板都压不住。这些“假礼”,就是元明清三代那些僵化的等级秩序、规规矩矩。出于汉人的直觉,厌恶这些是再正常不过了。
一、建用皇极
宋以前儒学,与宋明理学不是同一种思想。先秦至汉唐儒学的最高原则是天命与大中之道,而不是君主本身。朱熹重新解释《尚书·洪范》"皇极",从九畴排第五提高到最高优先级, 把它解释为 君主是天下的最高标准,天下围绕君主建立秩序。,从而改变了儒家的政治哲学。
二 · 定于一尊
元朝恢复科举时规定:四书、五经必须按照朱熹注释考试,使其成为与功名利禄直接挂钩的唯一标准。于是理学不再只是一个前朝有争议的一个"逆党"学说,而成为整个帝国唯一的意识形态。明清完全继承了这一制度。
三、诸夏之亡
《论语》中孔子其实始终强调,华夏共同体高于个人君臣关系。例如孔子称赞管仲,就是因为即使“不忠”,改事新君,只要能够保卫华夏,也仍然值得肯定。但程朱理学更加重视君臣名分、上下秩序、皇权连续性,于是出现一种新的逻辑:即使皇帝是异族,也不能没有皇帝。1908年孔令贻把德国皇帝威廉二世肖像迎进了孔府
四、天命之礼
孔子的礼,本质上来源于 天命。所以:礼约束君主,君主不能创造礼。而理学实践中却逐渐变成皇帝成为礼法的最终解释者。于是礼不再约束权力,而成为权力工具。
五、凡心之仁
基督教的本质是爱与诫二元一体的罪文化,那么发源自中国的东亚文化,本质就是仁与礼二元一体的耻文化。
基督教中所谓的爱,叫做“Agape”。这是一个专有词,它有多重要呢?欧洲所有国家,不管哪种语言,它的拼写方式都是一致的,一字不易。“Agape”的源头是神,它是一种具有普世性的博爱。而“仁”和“Agape”的区别在于,中国的“仁”,其源头是凡人,是“己所不欲勿施于人”,它是一种推己及人的有差等之爱。
华夷之辩是“礼”的边界,“礼”是对“仁”的约束。面对民族危机时,个人、家庭利益、官职利益、君臣秩序都会压倒共同体利益。因此许多人最终选择保身、顺从、投降,而不是抗清。
(注:这里其实用 “异端” 和 “有经人” 对比更加强烈)
六、知行合一
孔子、董仲舒的礼法,理论源头是天帝,实践中确实也按天帝至上来执行。而程朱理学的礼法,理论源头是天理,但是实践中,理学的礼法源头压根不是天理!在以前,经筵都是大儒给皇帝讲课,到了乾隆那,变成皇帝给大儒讲课了。
先秦两汉礼法的源头是神?因为礼法的源头就不能是一个具体的人!礼法是用来栓人的保守性,如果礼法的源头是人,那么栓着你的绳子就牵在那个人手上,你就是那个人的奴才,礼法就成了赤裸裸的等级压迫。
如果天帝无法约束皇权,那么天帝也不能保护皇权。天帝不能保护皇权,皇帝就只能自己保护自己,于是朱元璋废除了丞相制度。所谓明清飞速膨胀的君权,其实和南北朝盛产的疯子皇帝有异曲同工之妙。跟着龙椅遗传的精神病,本质是因为坐在龙椅上的人没有安全感。没有人相信天命,连皇帝本人都不相信自己真正“受命于天”。而对于儒生大臣,后人说张居正是“常务副皇帝”。
七、失礼之国
(作者的一些感想,比较杂,不引述了)
看完之后真过瘾。本来想着今天的键政就到这了。
特别是第二点,最近几年我逐渐从对“科举”的好感,降低了。学生时代总有那么一些“考功名”的亲切感,但是现实世界还是觉得“军功合伙人”更优。
但是有一个更大的疑惑,转念一想不对劲。于是补充一点我自己的观点。
皇帝这个岗位,从秦到唐,都是一把手承包了世俗君权,和神权的双重责任。皇帝在赵政之前实际上是两个岗位,大祭司负责给「帝」传话,王中王称皇负责行政。
宋以后皇帝把道德秩序这一块外包给儒生了,自己关起门做皇家经营了,剩下的全是算计。
说得直白点汉唐的皇帝还勉强要点b脸,遇到难堪的事,还得想办法给手下和民间一点说法。
宋以后就是无情的 打工 - 服从 叙事。
我是真的越来越看不起大怂国。我把内心抱怨说给AI,AI指出
宋代皇帝其实仍然非常受士大夫制约
我反驳:
例如宋代皇帝其实仍然非常受士大夫制约,需要合理分赃才能一起搜刮老百姓。造成有史以来遍地造反运动。
AI当时就不乐意了。把教科书和网上常见吹捧宋朝的资料抬出来了。比如说宋朝其实造反的规模和烈度没那么多
但是我想说,你把大半个中国都丢干净了,西夏 辽金的汉人躺棺材里了,当然北境无人“造反”了。因为别人都被占领了。
秦汉 隋唐造反不就是关中和河北人打架吗?这一毛病从姬发那一辈儿就没停过。天子这一岗位说的直白点就是给大家当调停人,pax sinica 。你宋说白了就一个节度使,偏安江淮一隅,还这么多造反的。呸!
AI 被这个角度刁钻的回答干懵逼了。说你这个框架,衡量皇朝优劣的标准不是"有没有起义",而是 能不能维持整个华夏共同体的秩序。那么很多评价都会变。最后还嘴硬一句,宋朝其实内部治理得很好啦,最终是被蒙古人迫不得已干趴下的。
我当时就火了。对蒙古你好意思讲“战争”?实际上周 秦 汉 唐 的草原治理能力,也是 “天命” 的支柱啊。周武王牧誓,手里拿着的就是牦牛尾巴!不是象征汉人农耕的的锄头!纵观宋、金两朝,对草原的经略就是完全失败的。垃圾!
因为刚刚前一阵子看到 《金朝对草原的减丁,为何遏制不了蒙古的崛起》 这里 cue 一下
宋辽金真是一群乡镇企业家暴发户械斗。烂得要命。唉。你仔细想一下宋吹,那些证据,多少是近代人牵强附会的?宋朝人自己觉得骄傲吗?给好评的,都是后世明清没当上官的文人吧?
天命最大的意义在哪里?给人指明前行的道路,给人以希望。即便黑暗中世纪教会和君权也是这么分工的。挫宋做到了啥?苟且罢了。
有人说,大宋“杯酒释兵权”终结了五代十国,功劳巨大。但从“天命”的角度讲,国之大事,在祀与戎。你老赵家没能给一个民族找到希望,也干不过架,你这个王权就没有存在的根基。说得直白一点,东亚这篇土地,从周天子那一辈人开始就是武装殖民模式。你不殖民,有的是蒙古人 女真人殖你的民。
其实一开始那个文章的框架来讨论一个具体的事就很有力度。如何评价北宋赵光义毁掉太原?
具体的事迹大家可以问下AI。正如文章里所说的,丢掉了 华夷之辩 这个“天命”。那别人河北幽州人全体投夷你也怨不了谁。你之后靖康之变都是报应。
网上对 “内亚” 的说法一直有巨大争议,阿姨那边一直说“武德注入”,实际上征服,殖民 和扩张这些说法太粗暴。但是如果天子不提供秩序,那么你也别怨替代秩序的出现;无德,丢天命,天命归别人。似乎就是这么简单的道理
政治空间是没有真空的
谁的组织度更高,谁的天命就更强。
联系到欧洲发家,启蒙运动 文艺复兴,是在抛弃君权 神权 这个思维定势下做到的。更厉害。
不过替代品似乎是——资本?一个侧面就是牛津剑桥主打神学专业,改成政治经济学
资本的扩张我觉得按照脉络去捋,是蒸汽机,发展纺织业,全球贸易。 归根结底是煤铁革命,但是仔细想,实际上是把战争的边界改成向几百亿年的太阳能存款挖出来消耗了。
农牧时代是拿当季的太阳能来pk。谁能提供摩擦最小的当季太阳能分配,谁就在古代“有德”,有“天命”
现代社会一样的。全球变暖,污染,绿色能源等,一直到社会公平正义。
天命天命,天就是天上射下来的能量,命就是草木人间生命。
哈哈哈这个解释如何?是不是很唯物。
天(能源)与命(生命负载)之间,能否实现最高效的转换与分配?
生命本身就是一个“负熵的过程”。生命存在的意义,就是把“天”射下来的能量,通过光合作用、通过食物链,转化为有序的社会结构、文明形态和思想结晶。
如果转换效率高、分配摩擦小: 生命、社会繁荣,这就叫“天命有归”。
如果中间商抽成太多,腐败、内卷、战略自残(如毁太原): 能量(天)射下来了,却无法高效转化为“命(繁荣)”,能量在内部耗散了,这就是天命将尽。
大宋这种对外又送又怂,把“皇帝”这个singleton强行改成“兄弟之国”, 对内三冗,取缔军功兑现,换成“理学”考试,用首都的局部繁荣掩盖整体的失败。就是丢 天命 的典型。
古代的战争:争夺的是对“天光(土地)”的占有权。
现代的危机:焦虑的是“天光(太阳能存款)”快烧完了,我们该如何重新设计“命(人类社会)”的分配效率。
政治的本质就是能量管理工程。
天命 在过去,看重国体和人君,现代看政治制度科技政策。资本这个玩意,纵然有那么多毛病,但是很好的执行了“天命”
2026-06-26 09:40:00
工作和业余也用AI写代码,大大小小项目都经历了。从 rules, skills, spec, agent 到 harness 都玩过了
从AI嘴里发现一条比较稳的套路——ADR文档
rule, skills, spec 这些东西最大的问题就是瞎jb指挥。ADR 的好处是记录why,以及决策演变历史。
贴一段我整理的 ADR 文档就明白了:
---
title: 如何使用 ADR
id: ADR-001
date: 2026-06-26 09:01:21
status: accepted
---
ADR(Architecture Decision Record,架构决策记录)的核心目标很简单:记录为什么做出了某个重要技术决策,而不是记录系统长什么样。
目前比较常见的是 MADR、Nygard ADR 两种风格,但组织方式都大同小异。
一个团队通常会按下面几个层次组织。
不要一个 ADR 写整个系统设计。
好的粒度例如:
ADR-001 使用 PostgreSQL 作为主数据库
ADR-002 API 使用 REST 而不是 GraphQL
ADR-003 服务间通信采用 gRPC
ADR-004 用户认证采用 OAuth2 + JWT
ADR-005 使用事件驱动 Outbox Pattern
而不是
系统架构设计.md
因为几年以后,很难知道某个结论为什么来的。
一般都会固定编号。
adr/
0001-use-postgresql.md
0002-use-rest-api.md
0003-use-grpc.md
0004-use-oauth2.md
编号一旦存在,就不要修改。
即使后来废弃:
0003-use-grpc.md
Status: Superseded by ADR-0018
这样引用不会失效。
一般都有状态。
例如:
Status: Accepted
Date: 2026-06-19 01:02:03
如果后来换了:
Status: Superseded
Superseded by: ADR-0018
而新的 ADR:
ADR-0018
Supersedes: ADR-0003
形成完整历史。
---
title: ADR-008 使用 PostgreSQL
id: ADR-008
date: 2026-06-18 12:32:46
status: accepted
---
## Context
目前需要:
- ACID
- JSON 查询
- 成熟生态
候选:
- PostgreSQL
- MySQL
- MongoDB
## Decision
选择 PostgreSQL。
## Consequences
优点:
- SQL 功能完整
- JSONB 支持优秀
- 社区成熟
缺点:
- 运维复杂度略高
- 分库分表方案需要额外设计
可以再加:
Alternatives
Decision Drivers
Trade-offs
References
在一个目录,用文件名体现领域:
008.backend.use-grpc.md
010.security.use-oauth.md
012.frontend.react-query.md
这样编号保持连续,查找也方便。
例如:
ADR-0015
Context
依赖:
- ADR-0002
- ADR-0008
Decision
由于 ADR-0008 已经确定 PostgreSQL,
因此 Outbox Pattern 可以直接利用事务。
形成决策网络,而不是孤立文档。
这是很多团队最容易犯的错误。
不要写:
Controller
Service
Repository
这是设计文档。
ADR 应该写:
为什么不用 MongoDB?
为什么不用 GraphQL?
为什么采用 Saga?
为什么拆成多个 Service?
为什么 Event Sourcing 被放弃?
重点永远是 Why,而不是 What。
过去很多团队会这样组织:
| 文档 | 回答的问题 |
|---|---|
| RFC / Proposal | 未来准备怎么做? |
| ADR | 为什么这样做? |
| Architecture Doc | 系统如何组织? |
| Design Doc | 某个功能如何实现? |
| Runbook | 如何运维? |
流程是 RFC → ADR → Design Doc → Code。
RFC 用于讨论方案,达成决策后沉淀为 ADR;随后具体实现细节写入设计文档,最终落实到代码。这样既保留了决策依据,又避免 ADR 演变成冗长的设计说明。
在AI 时代,更简洁,易维护的方式是:
AI编写的项目,到后期,泥潭就是大量的docs。ADR 的好处是不用修订,全面引用+supersed。保证决策链清晰,低上下文成本