mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-26 20:33:15 +00:00
test(c-core): 补跨语言开销基线 —— 数据反驳「C 比 Go 快」的直觉
codec.go 的注释写着「EstimateTokens 是否该留在 C 侧由 codec_bench_test.go 的实测数据决定,不要凭直觉断言」,但那个文件此前并不存在(悬空引用)。 plan.md 与设计文档也都在说「需先有真实延迟基线(当前没有)」。本提交把它补上。 ## 数据(ns/op,benchmem) | 基准 | C(经 cgo)| 纯 Go | 谁快 | |---|---:|---:|---| | ModelContextWindow(短 ASCII)| 175 | 38 | Go 快 4.6× | | EstimateTokens / 空串 | 100 | 0.43 | Go 快 230× | | EstimateTokens / 短 ASCII | 115 | 6.5 | Go 快 17× | | EstimateTokens / 短中文 | 100 | 29 | Go 快 3.4× | | EstimateTokens / 中 200 字 | 229 | 509 | C 快 2.2× | | EstimateTokens / 1KB 中文 | 840 | 2870 | C 快 3.4× | | EstimateTokens / 1KB ASCII | 2318 | 332 | Go 快 7× | | TruncateByTokens / 短中文 | 233 | 54 | Go 快 4.3× | | TruncateByTokens / 1KB 中文 | 3923 | 6918 | C 快 1.8× | (已用 -count 复测确认稳定;ascii_1k 的异常已单独隔离复测 3 次) ## 三条结论 1. **cgo 固定开销约 95–100 ns/次**,小输入下完全压倒算法差异。 2. C 只在**长中文**(UTF-8 步进重)上领先;长 ASCII 反而 Go 快 7× (Go 的 utf8.RuneCountInString 对 ASCII 有快路径,C 侧逐字节跑)。 3. ⇒ 判据应是「哪个在**真实输入分布**下真能变快」,不是「哪个看起来更底层」。 ## 对后续 C 化的影响(已写进 plan.md §七 与设计文档 §7.1) - EstimateTokens 的真高频点在 process.go:476 的逐事件循环与 resident.go:552。 字段分布不单一:Source 是短标签(Go 快 17× 那一档), Input/Response 是对话文本(长中文 C 快、短文本与长 ASCII Go 快)。 ⇒ 当前一刀切走 C 会让短串净亏;正确做法是按长度分派, 但须先用真实长度分布复测,不要凭推测动手。 - ModelContextWindow(provider.go:333)与 TruncateByTokens(tooldefs.go:38) 调用点单一、非热路径,开销在单次请求尺度上无关痛痒。 这不否定 C 化方向:协议编解码(JSON 解析、SSE 分片)处理长文本, 才是 C 的主场,也是比「把短函数搬过去」更合理的下一步。
This commit is contained in:
@ -275,13 +275,56 @@ C 化的正确性**不能靠「跑起来没崩」**,必须有可复现的对
|
||||
- 放 SDK:天然跨端复用,但要走 SDK 冻结与大版本流程
|
||||
2. `ha_json.c` 是**复用**(从 remotedevice 复制/提为公共)还是**新写**?
|
||||
复用会动 SDK 目录结构。
|
||||
3. 是否接受「C 化后内核体积/构建复杂度上升」换取延迟确定性?
|
||||
(需先有一个真实延迟基线,当前没有)
|
||||
4. **下一个切片选谁**?L1 剩下的是协议编解码(`parseOpenAICompatible*`、
|
||||
3. **下一个切片选谁**?L1 剩下的是协议编解码(`parseOpenAICompatible*`、
|
||||
`normalize*ToolCalls` 等,见 §三 L1 表);该层依赖 JSON 解析 ⇒ 先解第 2 题。
|
||||
|
||||
### 7.1 ★ 跨语言开销基线(实测已补,2026-09-25)
|
||||
|
||||
原 §七 写着「需先有真实延迟基线,当前没有」。现已补上
|
||||
(`internal/agent/api/codec_bench_test.go`,`go test -bench`):
|
||||
|
||||
| 基准 | C(经 cgo)| 纯 Go | 谁快 |
|
||||
|---|---:|---:|---|
|
||||
| `ModelContextWindow`(短 ASCII)| 175 ns | 38 ns | **Go 快 4.6×** |
|
||||
| `EstimateTokens` / 空串 | 100 ns | 0.43 ns | **Go 快 230×** |
|
||||
| `EstimateTokens` / 短 ASCII | 115 ns | 6.5 ns | **Go 快 17×** |
|
||||
| `EstimateTokens` / 短中文 | 100 ns | 29 ns | **Go 快 3.4×** |
|
||||
| `EstimateTokens` / 中200字 | 229 ns | 509 ns | C 快 2.2× |
|
||||
| `EstimateTokens` / 1KB 中文 | 840 ns | 2870 ns | C 快 3.4× |
|
||||
| `EstimateTokens` / 1KB ASCII | 2318 ns | 332 ns | **Go 快 7×** |
|
||||
| `TruncateByTokens` / 短中文 | 233 ns | 54 ns | **Go 快 4.3×** |
|
||||
| `TruncateByTokens` / 1KB 中文 | 3923 ns | 6918 ns | C 快 1.8× |
|
||||
|
||||
**结论(不要凭直觉,数据说话)**:
|
||||
|
||||
1. **cgo 的固定开销约 95–100 ns/次**,小输入下完全压倒算法差异。
|
||||
2. C 只在**长中文**(rune 密集、UTF-8 步进重)上明显领先;
|
||||
长 ASCII 反而 Go 快 7×(Go 的 `utf8.RuneCountInString` 对 ASCII
|
||||
有快路径,而 C 侧逐字节跑)。
|
||||
3. ⇒ **「C 比 Go 快」是错的**;正确表述是「在特定输入分布上更快」。
|
||||
|
||||
**对函数的建议(按调用分布)**:
|
||||
|
||||
- `ModelContextWindow`:调用点单一(`provider.go:333`,每请求一次),
|
||||
且输入是**短 ASCII** ⇒ 拿不到收益。但它应该是**冷路径**,
|
||||
175 ns 在单次请求尺度上无关痛痒——关键是别把它放到循环里。
|
||||
- `EstimateTokens`:**真正的高频点**在 `process.go:476` 的逐事件循环
|
||||
(对每条上下文事件算 `Source + Input + 40`)与 `resident.go:552`
|
||||
(对每条上下文算 `Input + Response`)。字段分布**不单一**:
|
||||
- `Source` 是短标签(`"qq"` / `"webui"`)⇒ 属 Go 快 17× 那一档
|
||||
- `Input` / `Response` 是对话文本,长度跨度大:长中文 C 快 2–3.4×,
|
||||
短文本与长 ASCII 则 Go 快 3–7×
|
||||
⇒ **没有单一答案**:当前一刀切走 C 会让短串净亏。
|
||||
正确做法是**按长度分派**(短走 Go、长中文走 C),
|
||||
但需先用真实长度分布复测——不要凭推测动手。
|
||||
- `TruncateByTokens`:调用点单一(`tooldefs.go:38`),非热路径。
|
||||
|
||||
**这不否定 C 化方向**,但把「选谁下一个 C 化」的判据从「哪个函数看起来底层」
|
||||
换成「**哪个在真实输入分布下真能变快**」。协议编解码(JSON 解析、SSE 分片)
|
||||
处理的正是**长文本**——那才是 C 的主场,也是下一步更合理的候选。
|
||||
|
||||
---
|
||||
|
||||
*实测记录:双路径、缓存跟踪、交叉编译、变异测试均于 2026-09-25 在本仓实测;
|
||||
工具链与 C 资产核实于本仓*
|
||||
*实测记录:双路径、缓存跟踪、交叉编译、变异测试、跨语言基准均于 2026-09-25
|
||||
在本仓实测;工具链与 C 资产核实于本仓*
|
||||
*初版核实:2026-09-24 · 落地更新:2026-09-25*
|
||||
|
||||
Reference in New Issue
Block a user