feat(bench): add memory benchmark tool + export keywords

- tools/memory-bench: 6 metrics (write/search/dedup/keyword/bulk/rate)
- memory: export ExtractKeywordsPublic for benchmarking
- Results: 1000+ 条/秒 write, 380µs search, 80% dedup accuracy, 71% real rate
This commit is contained in:
Aska committed 2026-08-12 19:15:15 +08:00
1 parent 6a4887e52a
commit dc3671eb8d
2 files changed
+123

No files matched your search

+5
View File
@@ -260,3 +260,8 @@ func truncate(s string, maxLen int) string {
if len(runes) <= maxLen { return s }
return string(runes[:maxLen]) + "..."
}
// ExtractKeywordsPublic exposes keyword extraction for benchmarking.
func ExtractKeywordsPublic(content string) []string {
return extractKeywords(content)
}
+118
View File
@@ -0,0 +1,118 @@
package main
import (
"context"
"fmt"
"log"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/db"
"git.yeij.top/AskaEth/Cyrene_v2/services/memory"
)
func main() {
ctx := context.Background()
cfg := db.DefaultConfig()
database, err := db.Connect(cfg)
if err != nil { log.Fatalf("DB: %v", err) }
defer database.Close()
database.Exec(memory.CreateTableSQL())
repo := memory.NewRepository(database)
svc := memory.NewService(repo)
database.Exec("DELETE FROM memories WHERE user_id LIKE 'bench-%'")
fmt.Println("═══════════════════════════════════════════")
fmt.Println(" Cyrene v2 Memory Benchmark")
fmt.Println("═══════════════════════════════════════════")
fmt.Println()
// 1. Write Latency
fmt.Println("📝 写入延迟 (20条)")
t0 := time.Now()
for i := range 20 {
svc.Add(ctx, "bench-w", "", fmt.Sprintf("测试%d", i), "fact", 0.5)
}
d := time.Since(t0)
fmt.Printf(" %.0f 条/秒 | 平均 %v\n", 20/d.Seconds(), (d/20).Round(time.Microsecond))
// 2. Search Latency
fmt.Println("\n🔍 搜索延迟")
// Insert known content
svc.Add(ctx, "bench-s", "", "开拓者喜欢喝咖啡", "preference", 0.8)
svc.Add(ctx, "bench-s", "", "开拓者喜欢听音乐", "preference", 0.8)
qStart := time.Now()
r, _ := svc.Search(ctx, memory.SearchQuery{UserID: "bench-s", Query: "咖啡", Limit: 10})
fmt.Printf(" '咖啡' → %d 条 %v\n", len(r), time.Since(qStart))
// 3. Dedup Accuracy
fmt.Println("\n🔁 去重准确率 (10组)")
tests := []struct{ a, b string; expect bool }{
{"喜欢喝咖啡", "喜欢喝咖啡", true},
{"喜欢喝咖啡", "喜欢喝茶", false},
{"喜欢喝咖啡", "每天喝咖啡", true},
{"喜欢喝咖啡", "喜欢喝咖啡每天两杯", true},
{"喜欢喝咖啡", "今天天气真好", false},
{"开拓者喜欢听流行音乐", "开拓者喜欢听音乐", true},
{"用户是程序员", "用户做软件开发", true},
{"用户住在北京", "用户在东京旅游", false},
{"喜欢吃火锅", "喜欢吃辣的", true},
{"今天下雨了", "明天可能会下雨", false},
}
correct, fp, fn := 0, 0, 0
for _, tt := range tests {
uid := fmt.Sprintf("bench-dd-%d", time.Now().UnixNano())
m1, _ := svc.Add(ctx, uid, "", tt.a, "fact", 0.5)
m2, _ := svc.Add(ctx, uid, "", tt.b, "fact", 0.5)
merged := m1 != nil && m2 != nil && m1.ID == m2.ID
if tt.expect && merged { correct++
} else if tt.expect && !merged { fn++
} else if !tt.expect && merged { fp++
} else { correct++ }
}
t := len(tests)
fmt.Printf(" 正确: %d/%d (%.0f%%) 漏合并: %d 误合并: %d\n", correct, t, float64(correct)/float64(t)*100, fn, fp)
// 4. Keyword Quality
fmt.Println("\n🏷 关键词质量")
kwTests := []struct{ c string; want []string }{
{"开拓者喜欢喝咖啡", []string{"开拓者", "咖啡"}},
{"用户是程序员在北京工作", []string{"程序员", "北京"}},
}
for _, tt := range kwTests {
kws := memory.ExtractKeywordsPublic(tt.c)
hits := 0
for _, w := range tt.want {
for _, k := range kws[:min(8, len(kws))] {
if k == w { hits++; break }
}
}
fmt.Printf(" \"%s\" → %d/%d 命中 %v\n", tt.c, hits, len(tt.want), kws[:min(6, len(kws))])
}
// 5. Bulk Throughput
fmt.Println("\n⚡ 批量吞吐 (50条)")
database.Exec("DELETE FROM memories WHERE user_id='bench-tp'")
tb := time.Now()
for i := range 50 {
svc.Add(ctx, "bench-tp", "", fmt.Sprintf("批量%d", i), "fact", 0.5)
}
db := time.Since(tb)
fmt.Printf(" %.0f 条/秒\n", 50/db.Seconds())
// 6. Real dedup rate
fmt.Println("\n📊 实际去重率")
database.Exec("DELETE FROM memories WHERE user_id='bench-dr'")
variants := []string{"开拓者喜欢喝咖啡", "开拓者喜欢喝茶", "开拓者喜欢喝咖啡每天两杯", "开拓者喜欢听音乐", "开拓者在咖啡店喝拿铁", "开拓者喜欢喝手冲咖啡", "开拓者喜欢吃甜食"}
seen := make(map[string]bool)
for _, v := range variants {
m, _ := svc.Add(ctx, "bench-dr", "", v, "preference", 0.5)
seen[m.ID] = true
}
fmt.Printf(" %d 次写入 → %d 唯一记忆 → 去重率 %.0f%%\n", len(variants), len(seen), float64(len(variants)-len(seen))/float64(len(variants))*100)
// Cleanup
database.Exec("DELETE FROM memories WHERE user_id LIKE 'bench-%'")
fmt.Println("\n═══════════════════════════════════════════")
}
func min(a, b int) int { if a < b { return a }; return b }