Skip to content

Commit 20fe70e

Browse files
authored
perf: more interning (#7636)
This PR adds interning of strings representing common integer values, which greatly speeds up "to string" operations on numbers, and updates some built-ins commonly used for this to make use of interned values where possible. This is "light" version of a previous PR that did this more aggressively, but also came with more caveats. Importantly, interning of new strings is now never done at "runtime", but only allowed at init time. The API for interning is marked experimental and should not relied upon by anyone who expects a stable API. Signed-off-by: Anders Eknert <[email protected]>
1 parent aa81e31 commit 20fe70e

37 files changed

+649
-334
lines changed

internal/edittree/edittree.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -723,15 +723,17 @@ func (e *EditTree) Unfold(path ast.Ref) (*EditTree, error) {
723723
return child.Unfold(path[1:])
724724
}
725725

726+
idxt := ast.InternedIntNumberTerm(idx)
727+
726728
// Fall back to looking up the key in e.value.
727729
// Extend the tree if key is present. Error otherwise.
728-
if v, err := x.Find(ast.Ref{ast.InternedIntNumberTerm(idx)}); err == nil {
730+
if v, err := x.Find(ast.Ref{idxt}); err == nil {
729731
// TODO: Consider a more efficient "Replace" function that special-cases this for arrays instead?
730-
_, err := e.Delete(ast.InternedIntNumberTerm(idx))
732+
_, err := e.Delete(idxt)
731733
if err != nil {
732734
return nil, err
733735
}
734-
child, err := e.Insert(ast.IntNumberTerm(idx), ast.NewTerm(v))
736+
child, err := e.Insert(idxt, ast.NewTerm(v))
735737
if err != nil {
736738
return nil, err
737739
}

internal/future/filter_imports.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,28 @@ func FilterFutureImports(imps []*ast.Import) []*ast.Import {
1919
return ret
2020
}
2121

22-
var keywordsTerm = ast.StringTerm("keywords")
23-
2422
// IsAllFutureKeywords returns true if the passed *ast.Import is `future.keywords`
2523
func IsAllFutureKeywords(imp *ast.Import) bool {
2624
path := imp.Path.Value.(ast.Ref)
2725
return len(path) == 2 &&
2826
ast.FutureRootDocument.Equal(path[0]) &&
29-
path[1].Equal(keywordsTerm)
27+
path[1].Equal(ast.InternedStringTerm("keywords"))
3028
}
3129

3230
// IsFutureKeyword returns true if the passed *ast.Import is `future.keywords.{kw}`
3331
func IsFutureKeyword(imp *ast.Import, kw string) bool {
3432
path := imp.Path.Value.(ast.Ref)
3533
return len(path) == 3 &&
3634
ast.FutureRootDocument.Equal(path[0]) &&
37-
path[1].Equal(keywordsTerm) &&
35+
path[1].Equal(ast.InternedStringTerm("keywords")) &&
3836
path[2].Equal(ast.StringTerm(kw))
3937
}
4038

4139
func WhichFutureKeyword(imp *ast.Import) (string, bool) {
4240
path := imp.Path.Value.(ast.Ref)
4341
if len(path) == 3 &&
4442
ast.FutureRootDocument.Equal(path[0]) &&
45-
path[1].Equal(keywordsTerm) {
43+
path[1].Equal(ast.InternedStringTerm("keywords")) {
4644
if str, ok := path[2].Value.(ast.String); ok {
4745
return string(str), true
4846
}

internal/runtime/runtime.go

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,6 @@ type Params struct {
2121
SkipKnownSchemaCheck bool
2222
}
2323

24-
var (
25-
configKey = ast.StringTerm("config")
26-
envKey = ast.StringTerm("env")
27-
versionKey = ast.StringTerm("version")
28-
commitKey = ast.StringTerm("commit")
29-
authorizationEnabledKey = ast.StringTerm("authorization_enabled")
30-
skipKnownSchemaCheckKey = ast.StringTerm("skip_known_schema_check")
31-
)
32-
3324
// Term returns the runtime information as an ast.Term object.
3425
func Term(params Params) (*ast.Term, error) {
3526

@@ -47,7 +38,7 @@ func Term(params Params) (*ast.Term, error) {
4738
return nil, err
4839
}
4940

50-
obj.Insert(configKey, ast.NewTerm(v))
41+
obj.Insert(ast.InternedStringTerm("config"), ast.NewTerm(v))
5142
}
5243

5344
env := ast.NewObject()
@@ -61,11 +52,11 @@ func Term(params Params) (*ast.Term, error) {
6152
}
6253
}
6354

64-
obj.Insert(envKey, ast.NewTerm(env))
65-
obj.Insert(versionKey, ast.StringTerm(version.Version))
66-
obj.Insert(commitKey, ast.StringTerm(version.Vcs))
67-
obj.Insert(authorizationEnabledKey, ast.InternedBooleanTerm(params.IsAuthorizationEnabled))
68-
obj.Insert(skipKnownSchemaCheckKey, ast.InternedBooleanTerm(params.SkipKnownSchemaCheck))
55+
obj.Insert(ast.InternedStringTerm("env"), ast.NewTerm(env))
56+
obj.Insert(ast.InternedStringTerm("version"), ast.StringTerm(version.Version))
57+
obj.Insert(ast.InternedStringTerm("commit"), ast.StringTerm(version.Vcs))
58+
obj.Insert(ast.InternedStringTerm("authorization_enabled"), ast.InternedBooleanTerm(params.IsAuthorizationEnabled))
59+
obj.Insert(ast.InternedStringTerm("skip_known_schema_check"), ast.InternedBooleanTerm(params.SkipKnownSchemaCheck))
6960

7061
return ast.NewTerm(obj), nil
7162
}

v1/ast/annotations.go

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,6 @@ const (
2323
annotationScopeSubpackages = "subpackages"
2424
)
2525

26-
var (
27-
scopeTerm = StringTerm("scope")
28-
titleTerm = StringTerm("title")
29-
entrypointTerm = StringTerm("entrypoint")
30-
descriptionTerm = StringTerm("description")
31-
organizationsTerm = StringTerm("organizations")
32-
authorsTerm = StringTerm("authors")
33-
relatedResourcesTerm = StringTerm("related_resources")
34-
schemasTerm = StringTerm("schemas")
35-
customTerm = StringTerm("custom")
36-
refTerm = StringTerm("ref")
37-
nameTerm = StringTerm("name")
38-
emailTerm = StringTerm("email")
39-
schemaTerm = StringTerm("schema")
40-
definitionTerm = StringTerm("definition")
41-
documentTerm = StringTerm(annotationScopeDocument)
42-
packageTerm = StringTerm(annotationScopePackage)
43-
ruleTerm = StringTerm(annotationScopeRule)
44-
subpackagesTerm = StringTerm(annotationScopeSubpackages)
45-
)
46-
4726
type (
4827
// Annotations represents metadata attached to other AST nodes such as rules.
4928
Annotations struct {
@@ -444,93 +423,93 @@ func (a *Annotations) toObject() (*Object, *Error) {
444423
if len(a.Scope) > 0 {
445424
switch a.Scope {
446425
case annotationScopeDocument:
447-
obj.Insert(scopeTerm, documentTerm)
426+
obj.Insert(InternedStringTerm("scope"), InternedStringTerm("document"))
448427
case annotationScopePackage:
449-
obj.Insert(scopeTerm, packageTerm)
428+
obj.Insert(InternedStringTerm("scope"), InternedStringTerm("package"))
450429
case annotationScopeRule:
451-
obj.Insert(scopeTerm, ruleTerm)
430+
obj.Insert(InternedStringTerm("scope"), InternedStringTerm("rule"))
452431
case annotationScopeSubpackages:
453-
obj.Insert(scopeTerm, subpackagesTerm)
432+
obj.Insert(InternedStringTerm("scope"), InternedStringTerm("subpackages"))
454433
default:
455-
obj.Insert(scopeTerm, StringTerm(a.Scope))
434+
obj.Insert(InternedStringTerm("scope"), StringTerm(a.Scope))
456435
}
457436
}
458437

459438
if len(a.Title) > 0 {
460-
obj.Insert(titleTerm, StringTerm(a.Title))
439+
obj.Insert(InternedStringTerm("title"), StringTerm(a.Title))
461440
}
462441

463442
if a.Entrypoint {
464-
obj.Insert(entrypointTerm, InternedBooleanTerm(true))
443+
obj.Insert(InternedStringTerm("entrypoint"), InternedBooleanTerm(true))
465444
}
466445

467446
if len(a.Description) > 0 {
468-
obj.Insert(descriptionTerm, StringTerm(a.Description))
447+
obj.Insert(InternedStringTerm("description"), StringTerm(a.Description))
469448
}
470449

471450
if len(a.Organizations) > 0 {
472451
orgs := make([]*Term, 0, len(a.Organizations))
473452
for _, org := range a.Organizations {
474453
orgs = append(orgs, StringTerm(org))
475454
}
476-
obj.Insert(organizationsTerm, ArrayTerm(orgs...))
455+
obj.Insert(InternedStringTerm("organizations"), ArrayTerm(orgs...))
477456
}
478457

479458
if len(a.RelatedResources) > 0 {
480459
rrs := make([]*Term, 0, len(a.RelatedResources))
481460
for _, rr := range a.RelatedResources {
482-
rrObj := NewObject(Item(refTerm, StringTerm(rr.Ref.String())))
461+
rrObj := NewObject(Item(InternedStringTerm("ref"), StringTerm(rr.Ref.String())))
483462
if len(rr.Description) > 0 {
484-
rrObj.Insert(descriptionTerm, StringTerm(rr.Description))
463+
rrObj.Insert(InternedStringTerm("description"), StringTerm(rr.Description))
485464
}
486465
rrs = append(rrs, NewTerm(rrObj))
487466
}
488-
obj.Insert(relatedResourcesTerm, ArrayTerm(rrs...))
467+
obj.Insert(InternedStringTerm("related_resources"), ArrayTerm(rrs...))
489468
}
490469

491470
if len(a.Authors) > 0 {
492471
as := make([]*Term, 0, len(a.Authors))
493472
for _, author := range a.Authors {
494473
aObj := NewObject()
495474
if len(author.Name) > 0 {
496-
aObj.Insert(nameTerm, StringTerm(author.Name))
475+
aObj.Insert(InternedStringTerm("name"), StringTerm(author.Name))
497476
}
498477
if len(author.Email) > 0 {
499-
aObj.Insert(emailTerm, StringTerm(author.Email))
478+
aObj.Insert(InternedStringTerm("email"), StringTerm(author.Email))
500479
}
501480
as = append(as, NewTerm(aObj))
502481
}
503-
obj.Insert(authorsTerm, ArrayTerm(as...))
482+
obj.Insert(InternedStringTerm("authors"), ArrayTerm(as...))
504483
}
505484

506485
if len(a.Schemas) > 0 {
507486
ss := make([]*Term, 0, len(a.Schemas))
508487
for _, s := range a.Schemas {
509488
sObj := NewObject()
510489
if len(s.Path) > 0 {
511-
sObj.Insert(pathTerm, NewTerm(s.Path.toArray()))
490+
sObj.Insert(InternedStringTerm("path"), NewTerm(s.Path.toArray()))
512491
}
513492
if len(s.Schema) > 0 {
514-
sObj.Insert(schemaTerm, NewTerm(s.Schema.toArray()))
493+
sObj.Insert(InternedStringTerm("schema"), NewTerm(s.Schema.toArray()))
515494
}
516495
if s.Definition != nil {
517496
def, err := InterfaceToValue(s.Definition)
518497
if err != nil {
519498
return nil, NewError(CompileErr, a.Location, "invalid definition in schema annotation: %s", err.Error())
520499
}
521-
sObj.Insert(definitionTerm, NewTerm(def))
500+
sObj.Insert(InternedStringTerm("definition"), NewTerm(def))
522501
}
523502
ss = append(ss, NewTerm(sObj))
524503
}
525-
obj.Insert(schemasTerm, ArrayTerm(ss...))
504+
obj.Insert(InternedStringTerm("schemas"), ArrayTerm(ss...))
526505
}
527506

528507
if len(a.Custom) > 0 {
529508
c, err := InterfaceToValue(a.Custom)
530509
if err != nil {
531510
return nil, NewError(CompileErr, a.Location, "invalid custom annotation %s", err.Error())
532511
}
533-
obj.Insert(customTerm, NewTerm(c))
512+
obj.Insert(InternedStringTerm("custom"), NewTerm(c))
534513
}
535514

536515
return &obj, nil

v1/ast/builtins.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ func RegisterBuiltin(b *Builtin) {
2020
BuiltinMap[b.Name] = b
2121
if len(b.Infix) > 0 {
2222
BuiltinMap[b.Infix] = b
23+
24+
InternStringTerm(b.Infix)
25+
}
26+
27+
InternStringTerm(b.Name)
28+
if strings.Contains(b.Name, ".") {
29+
InternStringTerm(strings.Split(b.Name, ".")...)
2330
}
2431
}
2532

@@ -3388,7 +3395,7 @@ func (b *Builtin) Ref() Ref {
33883395
ref := make(Ref, len(parts))
33893396
ref[0] = VarTerm(parts[0])
33903397
for i := 1; i < len(parts); i++ {
3391-
ref[i] = StringTerm(parts[i])
3398+
ref[i] = InternedStringTerm(parts[i])
33923399
}
33933400
return ref
33943401
}

v1/ast/compile.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -964,12 +964,7 @@ func (c *Compiler) buildComprehensionIndices() {
964964
}
965965
}
966966

967-
var (
968-
keywordsTerm = StringTerm("keywords")
969-
pathTerm = StringTerm("path")
970-
annotationsTerm = StringTerm("annotations")
971-
futureKeywordsPrefix = Ref{FutureRootDocument, keywordsTerm}
972-
)
967+
var futureKeywordsPrefix = Ref{FutureRootDocument, InternedStringTerm("keywords")}
973968

974969
// buildRequiredCapabilities updates the required capabilities on the compiler
975970
// to include any keyword and feature dependencies present in the modules. The
@@ -2534,17 +2529,15 @@ func createMetadataChain(chain []*AnnotationsRef) (*Term, *Error) {
25342529

25352530
metaArray := NewArray()
25362531
for _, link := range chain {
2537-
p := link.Path.toArray().
2538-
Slice(1, -1) // Dropping leading 'data' element of path
2539-
obj := NewObject(
2540-
Item(pathTerm, NewTerm(p)),
2541-
)
2532+
// Dropping leading 'data' element of path
2533+
p := link.Path[1:].toArray()
2534+
obj := NewObject(Item(InternedStringTerm("path"), NewTerm(p)))
25422535
if link.Annotations != nil {
25432536
annotObj, err := link.Annotations.toObject()
25442537
if err != nil {
25452538
return nil, err
25462539
}
2547-
obj.Insert(annotationsTerm, NewTerm(*annotObj))
2540+
obj.Insert(InternedStringTerm("annotations"), NewTerm(*annotObj))
25482541
}
25492542
metaArray = metaArray.Append(NewTerm(obj))
25502543
}
@@ -4642,6 +4635,8 @@ func rewriteComprehensionTerms(f *equalityFactory, node any) (any, error) {
46424635
})
46434636
}
46444637

4638+
var doubleEq = Equal.Ref()
4639+
46454640
// rewriteEquals will rewrite exprs under x as unification calls instead of ==
46464641
// calls. For example:
46474642
//
@@ -4656,7 +4651,6 @@ func rewriteComprehensionTerms(f *equalityFactory, node any) (any, error) {
46564651
// partial evaluation cases we do want to rewrite == to = to simplify the
46574652
// result.
46584653
func rewriteEquals(x any) (modified bool) {
4659-
doubleEq := Equal.Ref()
46604654
unifyOp := Equality.Ref()
46614655
t := NewGenericTransformer(func(x any) (any, error) {
46624656
if x, ok := x.(*Expr); ok && x.IsCall() {

0 commit comments

Comments
 (0)