this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue/ast/astutil: avoid walking to the root to reuse scopes

As suggested by Matthew, this can avoid some overhead when having
more than a few levels of scope nesting. Plus, a pointer is smaller
than a slice, so each scope is a bit smaller.

Even with the AWS schema, which tops at a scope depth of 14,
this does lead to a measurable savings in CPU cost.
On the flip side, dealing with pointers makes the code a bit trickier,
but overall it's a coin toss as we can remove two methods.

│ old │ mid │
│ sec/op │ sec/op vs base │
FmtAwsSchema 2.210 ± 1% 2.167 ± 1% -1.91% (p=0.001 n=8)

│ old │ mid │
│ B/op │ B/op vs base │
FmtAwsSchema 1.064Gi ± 0% 1.064Gi ± 0% -0.00% (p=0.005 n=8)

│ old │ mid │
│ allocs/op │ allocs/op vs base │
FmtAwsSchema 12.92M ± 0% 12.92M ± 0% ~ (p=0.063 n=8)

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Change-Id: Iea3af538bc94da349456f9e9918052a0e6ae192b
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1225195
Reviewed-by: Matthew Sackman <matthew@cue.works>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+39 -38
+28 -31
cue/ast/astutil/resolve.go
··· 83 83 // Unresolved identifiers are recorded in [ast.File.Unresolved]. 84 84 // It will not overwrite already resolved identifiers. 85 85 func Resolve(f *ast.File, errFn ErrFunc) { 86 - visitor := &scope{errFn: errFn, identFn: resolveIdent} 86 + stack := make([]*scope, 0, 8) 87 + visitor := &scope{ 88 + errFn: errFn, 89 + identFn: resolveIdent, 90 + scopeStack: &stack, 91 + } 87 92 ast.Walk(f, visitor.Before, nil) 88 93 } 89 94 ··· 91 96 // It will not overwrite already resolved values. 92 97 func ResolveExpr(e ast.Expr, errFn ErrFunc) { 93 98 f := &ast.File{} 94 - visitor := &scope{file: f, errFn: errFn, identFn: resolveIdent} 99 + stack := make([]*scope, 0, 8) 100 + visitor := &scope{ 101 + file: f, 102 + errFn: errFn, 103 + identFn: resolveIdent, 104 + scopeStack: &stack, 105 + } 95 106 ast.Walk(e, visitor.Before, nil) 96 107 } 97 108 ··· 109 120 nameFn func(name string) 110 121 errFn func(p token.Pos, msg string, args ...interface{}) 111 122 112 - // scopeStack is used to reuse scope allocations. Only set on the root scope. 113 - scopeStack []*scope 123 + // scopeStack is used to reuse scope allocations. 124 + // The pointer is shared between the root scope and all its children. 125 + scopeStack *[]*scope 114 126 } 115 127 116 128 type entry struct { ··· 119 131 field *ast.Field // Used for LabelAliases 120 132 } 121 133 122 - func (s *scope) rootScope() *scope { 123 - // If this ever shows up in a CPU profile because we have very deeply nested scopes, 124 - // we can consider a shortcut, such as a pointer to a shared slice or struct. 125 - for s.outer != nil { 126 - s = s.outer 127 - } 128 - return s 129 - } 130 - 131 134 func (s *scope) allocScope() *scope { 132 - root := s.rootScope() 133 - if n := len(root.scopeStack); n > 0 { 134 - scope := root.scopeStack[n-1] 135 - root.scopeStack = root.scopeStack[:n-1] 135 + if n := len(*s.scopeStack); n > 0 { 136 + scope := (*s.scopeStack)[n-1] 137 + *s.scopeStack = (*s.scopeStack)[:n-1] 136 138 return scope 137 139 } 138 - return &scope{index: make(map[string]entry, 4)} 140 + return &scope{ 141 + index: make(map[string]entry, 4), 142 + scopeStack: s.scopeStack, 143 + } 139 144 } 140 145 141 - // putScope is not a method on scope on purpose, as we don't want to repeat 142 - // calls to [scope.rootScope] in [scope.freeScopesUntil]. 143 - func putScope(root, s *scope) { 146 + func (s *scope) freeScope() { 144 147 // Ensure no pointers remain, which can hold onto memory. 145 - // We only reuse the index map capacity. 146 - *s = scope{index: s.index} 148 + // We only reuse the index map capacity, and keep the scopeStack pointer. 149 + *s = scope{index: s.index, scopeStack: s.scopeStack} 147 150 clear(s.index) 148 - root.scopeStack = append(root.scopeStack, s) 149 - } 150 - 151 - func (s *scope) freeScope() { 152 - root := s.rootScope() 153 - putScope(root, s) 151 + *s.scopeStack = append(*s.scopeStack, s) 154 152 } 155 153 156 154 // freeScopesUntil frees all scopes from s up to (but not including) 'ancestor'. 157 155 func (s *scope) freeScopesUntil(ancestor *scope) { 158 - root := s.rootScope() 159 156 for s != ancestor { 160 157 if s == nil { 161 158 panic("ancestor scope not found") 162 159 } 163 160 next := s.outer 164 - putScope(root, s) 161 + s.freeScope() 165 162 s = next 166 163 } 167 164 }
+11 -7
cue/ast/astutil/sanitize.go
··· 50 50 } 51 51 52 52 // Gather all names. 53 + stack := make([]*scope, 0, 8) 53 54 s := &scope{ 54 - errFn: z.errf, 55 - nameFn: z.addName, 56 - identFn: z.markUsed, 55 + errFn: z.errf, 56 + nameFn: z.addName, 57 + identFn: z.markUsed, 58 + scopeStack: &stack, 57 59 } 58 60 ast.Walk(f, s.Before, nil) 59 61 if z.errs != nil { ··· 61 63 } 62 64 63 65 // Add imports and unshadow. 66 + stack = stack[:0] 64 67 s = &scope{ 65 - file: f, 66 - errFn: z.errf, 67 - identFn: z.handleIdent, 68 - index: make(map[string]entry), 68 + file: f, 69 + errFn: z.errf, 70 + identFn: z.handleIdent, 71 + index: make(map[string]entry), 72 + scopeStack: &stack, 69 73 } 70 74 z.fileScope = s 71 75 ast.Walk(f, s.Before, nil)