fix: Scope() 返回快实现, 不再把后续操作丢回逐次反射
Scope() 原来是 tv.slow().Scope(), 返回的是 *refx —— 基于 reflect 的慢实现。
功能完全正常, 只是它之后的每一次 Get/Set 都退回逐次反射, 这次重写的收益
在 Scope 之后全部消失。
Scope("A") 之后再 Get 144.6ns / 7 allocs -> 48.7ns / 2 allocs 2.97x
对照: 不经 Scope 直接 Get 59.0ns / 2 allocs
改为自己走 walk 定位、克隆、再包成 *rfx。克隆复用 cloneForValueInput,
与 New(值) 同一套逻辑: 含引用成分才递归深拷贝, 纯值类型逐字节复制。
这类问题很容易在重构里悄悄失守 —— 交回慢实现不会有任何功能异常, 只是慢 3 倍,
测试不看动态类型就发现不了。所以 TestScopeReturnsFastImpl 直接断言返回的是
*rfx, 并验证过有效: 改回 &refx{} 后该用例失败。
顺带确认了其余 slow() 调用点都不会把慢实现泄漏给调用方:
Append/Delete 返回 v 自身, Keys/StringMap*/Slice* 返回的是具体 Go 类型。
另加 Scope 的语义测试(含引用子树 / 纯值子树 / map 子树 / 无参数整体克隆,
都断言原数据未被改写)与 78 个对拍用例。
This commit is contained in:
@@ -641,13 +641,25 @@ func (v rfx) slow() R {
|
||||
return &refx{value: ptrValueAt(v.td, v.ptr)}
|
||||
}
|
||||
|
||||
// Scope 返回指定路径的深度克隆,在克隆上的修改不影响原数据。
|
||||
//
|
||||
// 注意返回的必须是 *rfx 而不是 refx: Scope 的结果通常还要继续 Get/Set,
|
||||
// 交回慢实现的话后续操作就全部退回逐次反射了 —— 实测那样比直接 Get 慢 2.5 倍。
|
||||
func (v *rfx) Scope(p ...string) R {
|
||||
target := v.Get(p...)
|
||||
tv, ok := target.(*rfx)
|
||||
if !ok || !tv.valid() {
|
||||
if !v.valid() {
|
||||
return Nil
|
||||
}
|
||||
return tv.slow().Scope()
|
||||
target, ok := v.walk(newPathIter(p))
|
||||
if !ok {
|
||||
return Nil
|
||||
}
|
||||
// cloneForValueInput 与 New(值) 同一套逻辑: 含引用成分才递归深拷贝,
|
||||
// 纯值类型逐字节复制就已经独立了。两者都返回指向副本的指针。
|
||||
cloned := cloneForValueInput(valueAt(target.td, target.ptr))
|
||||
if !cloned.IsValid() {
|
||||
return Nil
|
||||
}
|
||||
return newRfx(cloned)
|
||||
}
|
||||
|
||||
func (v *rfx) Append(items ...any) R {
|
||||
|
||||
Reference in New Issue
Block a user