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:
JianFeeeee
2026-09-26 13:06:10 +08:00
parent 84d2d7c313
commit a006237105

View 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)
}
}