mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-26 12:23:23 +00:00
test(proc): 建立进程间通信的成本地板(判定「优化 IPC」的空间)
目标:回答「工具调用往返 30µs 里,非编解码的 ~20µs 花在哪、能否优化」。 方法:先立地板 —— 任何跨进程方案都有 OS 调度决定的下界。 三层对照(同机同会话,3000 次迭代): | 层 | ns/op | allocs | |---|---:|---:| | ① OS 调度地板(cat 子进程管道 echo,无协议无 JSON) | **16071** | 0 | | ② 最简 RPC(无载荷、不经共享帧) | **25840** | 20 | | ③ 纯编解码(共享段 write+read+compact,纯内存) | 10200 | 84 | ## 两条判据 1. **① 已占 ② 的 62%**:一个什么都不做的 echo(两次进程唤醒 + 两次管道读写)就要 16µs。⇒ RPC 层的成本主要是 **OS 调度**, 不是协议解析或 JSON 序列化。 2. **②−① 只剩约 10µs**:这才是协议层(JSON 帧 + pending map + channel 握手)可优化的全部空间,且其中还包含一次真实的 JSON 编解码往返。⇒ 「优化 IPC」的理论上限约为端到端 30µs 的三分之一。 ## 结论 跨进程数据面的**协议侧已接近其地板**。若要把端到端再压下去, 方向不是「优化协议」,而是**改变通信形态本身**: · 批量调用(一次往返做多件事,摊薄固定调度成本) · 或对高频小调用改走共享内存 + 自旋/事件通知(绕过两次进程唤醒) 两者都是架构级改动,不是参数调优。 ★ 这也解释了此前几轮的困惑:为什么 C 化数据面收益总是很小 —— 因为真正的大头(OS 调度 16µs)与语言无关。 验证:基准可复现;internal/plugin/proc 全量测试绿。
This commit is contained in:
81
internal/plugin/proc/ipc_floor_test.go
Normal file
81
internal/plugin/proc/ipc_floor_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package proc
|
||||
|
||||
// ipc_floor_test.go —— 进程间通信的**成本地板**(纯测量,判定「优化 IPC」是否值得)。
|
||||
//
|
||||
// ============================ 为什么需要这个文件 ============================
|
||||
// 本轮的目标是回答:「工具调用往返 30µs 里,那 ~20µs 非编解码部分花在哪、
|
||||
// 能否优化」。而回答这类问题必须先建立**地板**:任何跨进程方案都有一个
|
||||
// 由 OS 调度决定的下界,低于它是不可能达到的。
|
||||
//
|
||||
// 于是做了三层对照(同一台机、同一次会话):
|
||||
//
|
||||
// ① OS 调度地板 `cat` 子进程管道 echo(无协议、无 JSON、无分配)
|
||||
// ② 裸 RPC 最简 method、无载荷、无共享帧
|
||||
// ③ 纯编解码 共享段 write+read+compact(纯内存,不跨进程)
|
||||
//
|
||||
// ★ 判据:若 ① 已经接近 ②,则 RPC 层的开销主要是**OS 调度**而非协议/JSON;
|
||||
// 那么「优化 IPC」的空间就只剩下 ②−① 那一小段,而不是整个 ②。
|
||||
// 没有地板数,任何「还能再快 X%」的说法都是空话。
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// BenchmarkIPC_OSFloorRawPipeEcho 建立跨进程往返的**绝对地板**:
|
||||
// 一个 `cat` 子进程,父进程写一行、读回一行。不含任何协议解析。
|
||||
//
|
||||
// 这个是「无论怎么优化协议都不可能低于」的量级 —— 它只包含
|
||||
// 两次进程唤醒(父→子、子→父)+ 两次管道读写。
|
||||
func BenchmarkIPC_OSFloorRawPipeEcho(b *testing.B) {
|
||||
cmd := exec.Command("cat")
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
b.Fatalf("StdinPipe: %v", err)
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
b.Fatalf("StdoutPipe: %v", err)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
b.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = stdin.Close()
|
||||
_ = cmd.Wait()
|
||||
}()
|
||||
|
||||
msg := []byte("{\"id\":1,\"method\":\"x\"}\n")
|
||||
buf := make([]byte, 4096)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := stdin.Write(msg); err != nil {
|
||||
b.Fatalf("write: %v", err)
|
||||
}
|
||||
if _, err := stdout.Read(buf); err != nil {
|
||||
b.Fatalf("read: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkIPC_MinimalRPC 最简 RPC 往返:无载荷、不经共享帧。
|
||||
//
|
||||
// ★ 选 output.invoke 而不是 tool.invoke:前者不要求插件注册任何东西,
|
||||
// 插件会回一个「未实现」错误 —— 但**往返已经完成**。
|
||||
// 本基准测的是**传输成本**,因此应答内容无关紧要
|
||||
// (且 Call 的错误已被忽略,不会中断计时)。
|
||||
func BenchmarkIPC_MinimalRPC(b *testing.B) {
|
||||
bin := buildBenchPlugin(b, "echoplugin.go")
|
||||
p, err := Spawn("echo", bin, Options{Handler: noopHandler})
|
||||
if err != nil {
|
||||
b.Fatalf("Spawn: %v", err)
|
||||
}
|
||||
defer p.Kill()
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = p.Call(MethodOutputInvoke, nil)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user