性能
govalid 使用纯反射——没有代码生成,没有需要预热的缓存。下面的 数据来自 Apple M1 Pro 上的 Go 1.26(go test -bench=. -benchmem):
text
BenchmarkCheck_Simple_Valid-10 745966 1655 ns/op 1136 B/op 29 allocs/op
BenchmarkCheck_Simple_Invalid-10 713349 1644 ns/op 1160 B/op 31 allocs/op
BenchmarkCheck_Rich_Valid-10 262418 4748 ns/op 3216 B/op 73 allocs/op
BenchmarkParseRules-10 2852902 412 ns/op 520 B/op 14 allocs/op对一个 10 字段的 "rich" 结构体(含手机号、邮箱、长度与范围检查), 单核约 21 万次校验/秒,无需任何额外优化。
各项耗时
| 操作 | 成本 |
|---|---|
每个标签的 parseRules | ~400ns / 14 次分配 |
| 每个校验器调用 | 各自数 ns,正则驱动的略高 |
| 反射遍历字段 | 主导内存分配 |
| 本地化消息渲染 | 每条非平凡模板一次 strings.NewReplacer |
热点路径与建议
- 正则只编译一次。 内置模式都是包级
regexp.MustCompile。 自定义校验器也应遵循同样的模式。 - 复用结构体类型。
parseRules在单次Check调用内会缓存 按标签的规则切片,但跨调用并不缓存。 - 请求级别校验。 govalid 足够快,可以在 HTTP handler 里同步调用, 无需"校验缓存"。
- 避免巨型
list:规则。 每个候选值都用fmt.Sprintf("%v", value)比较。几十个值没问题;上千个就改用 自定义校验器,内部用map[string]struct{}。
并发
Check 可以被并发调用。热点路径上的包级状态(Checkers、各 locale 模板 map)不支持并发写——请在程序启动、goroutine 开始命中 Check 之前就把自定义校验器与模板注册好。
go test -race 已经覆盖了共享 struct 与独立 struct 两类并发负载, 都是干净的。
Fuzz 覆盖
仓库提供两个 fuzz 目标:
sh
go test -run=^$ -fuzz=FuzzCheck -fuzztime=10s ./...
go test -run=^$ -fuzz=FuzzParseRules -fuzztime=10s ./...两者累计百万级执行,没有产生任何 panic。如果你发现一个会触发 崩溃的输入,请提交 issue 并附上种子。