this repo has no description
0
fork

Configure Feed

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

cue/ast/astutil: reuse scope allocations

By pooling them in a slice in the root scope,
and using defer function calls to put them back there once done.

While here, fix two faulty godocs.

│ old │ new │
│ sec/op │ sec/op vs base │
FmtAwsSchema 2.286 ± 1% 2.199 ± 2% -3.80% (p=0.000 n=8)

│ old │ new │
│ B/op │ B/op vs base │
FmtAwsSchema 1.526Gi ± 0% 1.064Gi ± 0% -30.28% (p=0.000 n=8)

│ old │ new │
│ allocs/op │ allocs/op vs base │
FmtAwsSchema 15.33M ± 0% 12.92M ± 0% -15.77% (p=0.000 n=8)

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

+68 -13
+68 -13
cue/ast/astutil/resolve.go
··· 87 87 ast.Walk(f, visitor.Before, nil) 88 88 } 89 89 90 - // Resolve resolves all identifiers in an expression. 90 + // ResolveExpr resolves all identifiers in an expression. 91 91 // It will not overwrite already resolved values. 92 92 func ResolveExpr(e ast.Expr, errFn ErrFunc) { 93 93 f := &ast.File{} ··· 95 95 ast.Walk(e, visitor.Before, nil) 96 96 } 97 97 98 - // A Scope maintains the set of named language entities declared 98 + // A scope maintains the set of named language entities declared 99 99 // in the scope and a link to the immediately surrounding (outer) 100 100 // scope. 101 101 type scope struct { ··· 108 108 identFn func(s *scope, n *ast.Ident) bool 109 109 nameFn func(name string) 110 110 errFn func(p token.Pos, msg string, args ...interface{}) 111 + 112 + // scopeStack is used to reuse scope allocations. Only set on the root scope. 113 + scopeStack []*scope 111 114 } 112 115 113 116 type entry struct { ··· 116 119 field *ast.Field // Used for LabelAliases 117 120 } 118 121 119 - func newScope(f *ast.File, outer *scope, node ast.Node, decls []ast.Decl) *scope { 120 - const n = 4 // initial scope capacity 121 - s := &scope{ 122 - file: f, 123 - outer: outer, 124 - node: node, 125 - index: make(map[string]entry, n), 126 - identFn: outer.identFn, 127 - nameFn: outer.nameFn, 128 - errFn: outer.errFn, 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 + 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] 136 + return scope 137 + } 138 + return &scope{index: make(map[string]entry, 4)} 139 + } 140 + 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) { 144 + // Ensure no pointers remain, which can hold onto memory. 145 + // We only reuse the index map capacity. 146 + *s = scope{index: s.index} 147 + 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) 154 + } 155 + 156 + // freeScopesUntil frees all scopes from s up to (but not including) 'ancestor'. 157 + func (s *scope) freeScopesUntil(ancestor *scope) { 158 + root := s.rootScope() 159 + for s != ancestor { 160 + if s == nil { 161 + panic("ancestor scope not found") 162 + } 163 + next := s.outer 164 + putScope(root, s) 165 + s = next 129 166 } 167 + } 168 + 169 + func newScope(f *ast.File, outer *scope, node ast.Node, decls []ast.Decl) *scope { 170 + s := outer.allocScope() 171 + s.file = f 172 + s.outer = outer 173 + s.node = node 174 + s.inField = false 175 + s.identFn = outer.identFn 176 + s.nameFn = outer.nameFn 177 + s.errFn = outer.errFn 178 + 130 179 for _, d := range decls { 131 180 switch x := d.(type) { 132 181 case *ast.Field: ··· 313 362 func (s *scope) Before(n ast.Node) bool { 314 363 switch x := n.(type) { 315 364 case *ast.File: 316 - s := newScope(x, s, x, x.Decls) 365 + s = newScope(x, s, x, x.Decls) 366 + defer s.freeScope() 317 367 // Support imports. 318 368 for _, d := range x.Decls { 319 369 ast.Walk(d, s.Before, nil) ··· 322 372 323 373 case *ast.StructLit: 324 374 s = newScope(s.file, s, x, x.Elts) 375 + defer s.freeScope() 325 376 for _, elt := range x.Elts { 326 377 ast.Walk(elt, s.Before, nil) 327 378 } 328 379 return false 329 380 330 381 case *ast.Comprehension: 382 + outer := s 331 383 s = scopeClauses(s, x.Clauses) 384 + defer s.freeScopesUntil(outer) 332 385 ast.Walk(x.Value, s.Before, nil) 333 386 return false 334 387 ··· 351 404 break 352 405 } 353 406 s = newScope(s.file, s, x, nil) 407 + defer s.freeScope() 354 408 if alias != nil { 355 409 if name, _, _ := ast.LabelName(alias.Ident); name != "" { 356 410 s.insert(name, x, alias, nil) ··· 398 452 // TODO: this should move into Before once decl attributes 399 453 // have been fully deprecated and embed attributes are introduced. 400 454 s = newScope(s.file, s, x, nil) 455 + defer s.freeScope() 401 456 s.insert(alias.Ident.Name, alias, x, nil) 402 457 n = alias.Expr 403 458 }