mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
## 背景 仓库早已迁移到 GitHub(`github.com/JianFeeeee/HomeAgent`,CI/CD 都在那边跑), gitcode 保留为**国内只读镜像**(`ci.yml` 的 sync-gitcode-main 快进推送)。 但 go.mod 的 module 路径一直是 `gitcode.com/JianFeeeee/...` —— 既与权威源 不符,`meta.SourceURL`(AGPL §13 的源码提供入口)也指向镜像而非实际运行的代码。 ## 关键区分(这是本次改动安全的前提) 两个 module 是**不同**的,影响面差一个数量级: | module | 谁依赖 | 改它的后果 | |---|---|---| | `.../HomeAgent`(主仓) | **只有主仓自己** | 内部重构,零外部影响 | | `.../homeagent-sdk`(SDK 仓) | 主仓 49 文件 + 所有外部插件 | 需重编插件 | ★ 实测确认:**插件不 import 主仓 module**(`strings qq/plugin.bin | grep HomeAgent` = 0), 只依赖 SDK 仓。而插件与内核的边界是 **RPC 方法名字符串** (`plugin.init` / `tool.invoke`…,见 proc/protocol.go),**与 Go module 名无关**。 ⇒ module 改名**不改变任何 wire 协议**,现网已装的插件照常运行。 SDK 的 GitHub 仓名是 `homeagentsdk`(无连字符),module 路径必须与之精确匹配, 否则 `go get` 会因路径不符失败,故取 `github.com/JianFeeeee/homeagentsdk`。 ## 改动 - `go.mod` / `go.sum`:两个 module 路径 + require/replace - 277 个 Go 文件 + Makefile + 打包脚本的 import 与 ldflags 路径 - SDK 仓 `meta.CoreModule`、`tools/hmapdev` 的 module 声明与工程模板 - 对外文档/链接的仓库地址(README、PLUGIN_DEV、OVERVIEW 等) ## 明确**保留** gitcode 的地方(镜像机制,不能动) - `GITCODE_TOKEN`、`GC_URL="…@gitcode.com"`(CI 同步 job) - `gitcode.com/api/v5/...`(release 附件上传) - git remote `gitcode`(镜像拉取/推送) - `docs/zh/ci-cd-runbook.md`(整篇讲镜像运维) ## 验证 - `go build ./...` / `go vet ./...` 绿 - `go test ./... -count=1` → 48 包 ok / 0 FAIL - hmapdev 工具链编译通过(`third_party/homeagent-sdk/tools/hmapdev`) - 全仓残留检查:module 形态的 `gitcode.com/JianFeeeee` = 0 - 镜像机制抽验:GC_URL / @gitcode / api/v5 / remote 均完好 ## 顺带修正 `hmapdev/templates.go` 的注释此前写「gitcode 的模块不在 proxy.golang.org 上」—— module 已迁 GitHub,该论断不再成立。改为如实说明**为何仍默认 replace 到本机 SDK**: 离线可构建、避免版本漂移、内网可用。理由与托管地无关,故 replace 保留。
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/homeagentsdk/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()
|
||
}
|
||
}
|