mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 14:57:38 +00:00
## 为什么必须做
SDK 仓(独立仓库)已把 module 从 `github.com/JianFeeeee/homeagentsdk`
改名为 `hologramagentsdk`。主仓通过 `replace` 直接引用它的**物理目录**:
replace github.com/JianFeeeee/homeagentsdk => ./third_party/homeagent-sdk
一旦 SDK 侧改成新 module 名,主仓立刻炸:
go: ./third_party/homeagent-sdk@ used for two different module paths
(github.com/JianFeeeee/hologramagentsdk and github.com/JianFeeeee/homeagentsdk)
Go **不允许**同一目录被两个 module 路径 replace。所以不能"新旧并存",
只能把主仓 59 个文件的 import 全部改到新名,并删掉旧 replace。
## 一个被证伪的前提
我最初按"third_party/homeagent-sdk 是独立仓库,不能在此仓修改其源码"
来处理,并因此判断两个仓的改动可以隔离。实际上主仓是靠 `replace` 指向
该目录的物理副本 —— **两个仓的改动不可能真正隔离**:SDK 侧改 module 名,
主仓必然跟着改 import。
这个前提应该在做任何改动之前用 `go list -m` 验证,而不是假设。
## 连带修掉两处
| 缺陷 | 性质 |
|---|---|
| `TestLuaSDKMockSingleSource` 失败 | SDK 的 `sdk/lua/sdk.lua` 变了,主仓内嵌副本未同步(跑 `scripts/sync-lua-sdk.sh` 修复) |
| `sdkModuleNames` 分散两处 | 已在 SDK 仓合并为单一数据源(见 SDK 仓 725732b) |
第一条是**副本一致性判据抓到的**,不是我自己发现的 —— 这类判据的价值在此:
两个仓必然有副本,靠人记住"改了 A 要同步 B"是做不到的。
## 验证
- `go build ./...` / `go vet ./...` 干净
- `go test ./... -count=1`:**58 包 0 失败**
- `internal/plugin`(Lua SDK 副本一致性)+ `internal/sdk` 全绿
- 主仓 `third_party/homeagent-sdk/` 与 SDK 仓 `main` 无差异
271 lines
7.0 KiB
Go
271 lines
7.0 KiB
Go
package proc
|
||
|
||
// shm_profile_test.go —— 共享内存数据面的**成本分解**基准(纯测量,不改实现)。
|
||
//
|
||
// ============================ 为什么要这个文件 ============================
|
||
// 「共享内存该用 C 实现」是个直觉,本文件负责把它变成数据。
|
||
//
|
||
// 端到端(跨进程)工具调用往返实测 35.9µs(inline/small),
|
||
// 而 BenchmarkSegmentWriteAllReadInto(纯编解码)3.1µs —— 差 ~9%。
|
||
// 但那 3.1µs **不是同质的**:里面混着三类成本,只有分开测才知道
|
||
// 哪一类是 C 的甜区、哪一类根本不该 C 化:
|
||
//
|
||
// ① 段内字节搬运(copy / string(b)) —— C 的甜区(memcpy)
|
||
// ② **json.Marshal / Unmarshal** —— 反射,C 无优势(且难保证逐值一致)
|
||
// ③ 描述符/游标记账(小字段多、极频繁) —— 固定开销,非内存带宽
|
||
//
|
||
// 判据:若②占大头,则 C 化整条编解码**不划算**(跨语言重建 JSON 语义
|
||
// 的成本远高于省下的 memcpy)—— 这正是 ha_json_scan 那三刀学到的事。
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strings"
|
||
"testing"
|
||
|
||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||
)
|
||
|
||
// profileCtx 构造一组「接近真实」的 StageContext。
|
||
func profileCtx(toolResults, ctxMsgs int) *pubsdk.StageContext {
|
||
sc := &pubsdk.StageContext{
|
||
Phase: pubsdk.StageAfterToolcall,
|
||
RawMessage: strings.Repeat("用户输入的一段话。", 8),
|
||
UserID: "u1",
|
||
LLMText: strings.Repeat("模型输出的文本内容。", 16),
|
||
FinalText: strings.Repeat("最终给用户的回答。", 4),
|
||
}
|
||
for i := 0; i < toolResults; i++ {
|
||
sc.ToolResults = append(sc.ToolResults, pubsdk.ToolResult{
|
||
CallID: "call_" + strings.Repeat("x", 8),
|
||
Name: "tool_name_" + string(rune('a'+i%26)),
|
||
Result: strings.Repeat("工具返回的结果内容。", 6),
|
||
})
|
||
}
|
||
for i := 0; i < ctxMsgs; i++ {
|
||
sc.ContextMsgs = append(sc.ContextMsgs, map[string]interface{}{
|
||
"role": "assistant",
|
||
"content": strings.Repeat("历史消息内容。", 6),
|
||
})
|
||
}
|
||
return sc
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// ① 整体:write + read + compact(对齐现有 BenchmarkSegmentWriteAllReadInto)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
func BenchmarkShm_Whole(b *testing.B) {
|
||
for _, n := range []int{0, 2, 8} {
|
||
sc := profileCtx(n, n)
|
||
host, err := NewHost()
|
||
if err != nil {
|
||
b.Fatalf("NewHost: %v", err)
|
||
}
|
||
seg := host.Segment()
|
||
b.Run(sizeName(n), func(b *testing.B) {
|
||
b.ReportAllocs()
|
||
for i := 0; i < b.N; i++ {
|
||
if err := seg.WriteAll(sc); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
if err := seg.ReadInto(sc); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
seg.Compact()
|
||
}
|
||
})
|
||
host.Close()
|
||
}
|
||
}
|
||
|
||
func sizeName(n int) string {
|
||
switch n {
|
||
case 0:
|
||
return "empty"
|
||
case 2:
|
||
return "small"
|
||
default:
|
||
return "large"
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// ② 只测 JSON 编解码(②类成本:Marshal + Unmarshal)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
func BenchmarkShm_JsonOnly(b *testing.B) {
|
||
for _, n := range []int{0, 2, 8} {
|
||
sc := profileCtx(n, n)
|
||
host, err := NewHost()
|
||
if err != nil {
|
||
b.Fatalf("NewHost: %v", err)
|
||
}
|
||
seg := host.Segment()
|
||
// 先落段,得到真实的 JSON 字节
|
||
if err := seg.WriteAll(sc); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
var blobs [][]byte
|
||
for _, f := range []stageField{fToolCalls, fToolResults, fContextMsgs} {
|
||
bl, err := seg.read(seg.getDesc(f))
|
||
if err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
if len(bl) > 0 {
|
||
cp := make([]byte, len(bl))
|
||
copy(cp, bl)
|
||
blobs = append(blobs, cp)
|
||
}
|
||
}
|
||
var tcs []pubsdk.ToolCall
|
||
var trs []pubsdk.ToolResult
|
||
var cms []map[string]interface{}
|
||
b.Run(sizeName(n), func(b *testing.B) {
|
||
b.ReportAllocs()
|
||
for i := 0; i < b.N; i++ {
|
||
for j, bl := range blobs {
|
||
switch j % 3 {
|
||
case 0:
|
||
_ = json.Unmarshal(bl, &tcs)
|
||
case 1:
|
||
_ = json.Unmarshal(bl, &trs)
|
||
default:
|
||
_ = json.Unmarshal(bl, &cms)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
// Marshal 侧
|
||
b.Run(sizeName(n)+"/marshal", func(b *testing.B) {
|
||
b.ReportAllocs()
|
||
for i := 0; i < b.N; i++ {
|
||
_, _ = json.Marshal(tcs)
|
||
_, _ = json.Marshal(trs)
|
||
_, _ = json.Marshal(cms)
|
||
}
|
||
})
|
||
host.Close()
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// ③ 只测段内字节搬运(①类成本:copy / string(b))—— C 的甜区
|
||
// ---------------------------------------------------------------------------
|
||
|
||
func BenchmarkShm_ByteCopyOnly(b *testing.B) {
|
||
host, err := NewHost()
|
||
if err != nil {
|
||
b.Fatalf("NewHost: %v", err)
|
||
}
|
||
defer host.Close()
|
||
seg := host.Segment()
|
||
seg.Compact()
|
||
|
||
sizes := []int{0, 64, 1024, 16384, 131072}
|
||
for _, sz := range sizes {
|
||
src := make([]byte, sz)
|
||
for i := range src {
|
||
src[i] = byte('a' + i%26)
|
||
}
|
||
b.Run(sizeName2(sz), func(b *testing.B) {
|
||
b.SetBytes(int64(sz))
|
||
b.ReportAllocs()
|
||
for i := 0; i < b.N; i++ {
|
||
off, err := seg.alloc(sz)
|
||
if err != nil {
|
||
seg.Compact()
|
||
off, err = seg.alloc(sz)
|
||
if err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
base := seg.arenaBase()
|
||
copy(seg.data[base+off:base+off+uint32(sz)], src)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func sizeName2(n int) string {
|
||
switch {
|
||
case n == 0:
|
||
return "0B"
|
||
case n < 1024:
|
||
return itoa(n) + "B"
|
||
default:
|
||
return itoa(n/1024) + "KB"
|
||
}
|
||
}
|
||
|
||
func itoa(n int) string {
|
||
if n == 0 {
|
||
return "0"
|
||
}
|
||
var buf [20]byte
|
||
i := len(buf)
|
||
for n > 0 {
|
||
i--
|
||
buf[i] = byte('0' + n%10)
|
||
n /= 10
|
||
}
|
||
return string(buf[i:])
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// ④ 只测描述符记账(③类成本:18 个 Slice 描述符的 get/set)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
func BenchmarkShm_DescOnly(b *testing.B) {
|
||
host, err := NewHost()
|
||
if err != nil {
|
||
b.Fatalf("NewHost: %v", err)
|
||
}
|
||
defer host.Close()
|
||
seg := host.Segment()
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
for f := stageField(0); f < stageFieldCount; f++ {
|
||
sl := seg.getDesc(f)
|
||
seg.setDesc(f, sl)
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// ⑤ write / read 分离,并给出「C 化三类成本各自的天花板」
|
||
// ---------------------------------------------------------------------------
|
||
|
||
func BenchmarkShm_Split(b *testing.B) {
|
||
for _, n := range []int{0, 2, 8} {
|
||
sc := profileCtx(n, n)
|
||
host, err := NewHost()
|
||
if err != nil {
|
||
b.Fatalf("NewHost: %v", err)
|
||
}
|
||
seg := host.Segment()
|
||
|
||
b.Run(sizeName(n)+"/write", func(b *testing.B) {
|
||
b.ReportAllocs()
|
||
for i := 0; i < b.N; i++ {
|
||
if err := seg.WriteAll(sc); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
seg.Compact()
|
||
}
|
||
})
|
||
if err := seg.WriteAll(sc); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
b.Run(sizeName(n)+"/read", func(b *testing.B) {
|
||
b.ReportAllocs()
|
||
for i := 0; i < b.N; i++ {
|
||
if err := seg.ReadInto(sc); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
})
|
||
host.Close()
|
||
}
|
||
}
|