diff --git a/internal/plugin/proc/ipc_floor_test.go b/internal/plugin/proc/ipc_floor_test.go new file mode 100644 index 0000000..fd7e706 --- /dev/null +++ b/internal/plugin/proc/ipc_floor_test.go @@ -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) + } +}