Skip to content

Commit

Permalink
internal/filetypes: speed up common case
Browse files Browse the repository at this point in the history
Creating a `*build.File` value is somewhat expensive in the general
case, but the general case is fairly unusual: it's much more common to
have files interpreted according to their extension only with defaults
for all other encoding attributes.

Make this operation more efficient (no CUE value lookup required) for
this common case by caching the resulting `*build.File` values for each
of the known file extensions at init time.

While we're about it, change the existing "common case" logic to use a
check that matches the actual invariants of the common case for CUE:
specifically the default encoding for CUE uses `form: ""` not `form:
"schema"`: this makes a difference in subsequent CLs in the checks for
whether a given parsed CUE file can be cached.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: I46f8c823cca08a44a09dece8d5e9a3df55bcf928
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1197557
Reviewed-by: Paul Jolly <paul@myitcv.io>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
  • Loading branch information
rogpeppe committed Jul 11, 2024
1 parent 27adbac commit fed43b0
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 24 deletions.
25 changes: 14 additions & 11 deletions internal/filetypes/filetypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func FromFile(b *build.File, mode Mode) (*FileInfo, error) {
// isolation without interference from evaluating these files.
if mode == Input &&
b.Encoding == build.CUE &&
b.Form == build.Schema &&
b.Form == "" &&
b.Interpretation == "" {
return &FileInfo{
File: b,
Expand Down Expand Up @@ -175,11 +175,9 @@ func ParseArgs(args []string) (files []*build.File, err error) {
if !fileVal.Exists() {
if len(a) == 1 && strings.HasSuffix(a[0], ".cue") {
// Handle majority case.
files = append(files, &build.File{
Filename: a[0],
Encoding: build.CUE,
Form: build.Schema,
})
f := *fileForCUE
f.Filename = a[0]
files = append(files, &f)
hasFiles = true
continue
}
Expand Down Expand Up @@ -269,15 +267,20 @@ func ParseFileAndType(file, scope string, mode Mode) (*build.File, error) {
// Quickly discard files which we aren't interested in.
// These cases are very common when loading `./...` in a large repository.
typesInit()
if scope == "" {
if scope == "" && file != "-" {
ext := fileExt(file)
if file == "-" {
// not handled here
} else if ext == "" {
if ext == "" {
return nil, errors.Newf(token.NoPos, "no encoding specified for file %q", file)
} else if !knownExtensions[ext] {
}
f, ok := fileForExt[ext]
if !ok {
return nil, errors.Newf(token.NoPos, "unknown file extension %s", ext)
}
if mode == Input {
f1 := *f
f1.Filename = file
return &f1, nil
}
}
modeVal, fileVal, err := parseType(scope, mode)
if err != nil {
Expand Down
20 changes: 10 additions & 10 deletions internal/filetypes/types.cue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
Expand Down Expand Up @@ -56,15 +56,15 @@ package build
attributes?: bool // include/allow attributes
}

// knownExtensions derives all the known file extensions
// from those that are mentioned in modes,
// allowing us to quickly discard files with unknown extensions.
knownExtensions: {
for mode in modes
for ext, _ in mode.extensions {
(ext): true
}
}
// fileForExtVanilla holds the extensions supported in
// input mode with scope="" - the most common form
// of file type to evaluate.
//
// It's also used as a source of truth for all known file
// extensions as all modes define attributes for
// all file extensions. If that ever changed, we'd need
// to change this.
fileForExtVanilla: modes.input.extensions

// modes sets defaults for different operational modes.
modes: [string]: {
Expand Down
19 changes: 16 additions & 3 deletions internal/filetypes/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,38 @@ package filetypes

import (
_ "embed"
"fmt"
"sync"

"cuelang.org/go/cue"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/cuecontext"
)

//go:embed types.cue
var typesCUE string

var typesValue cue.Value
var knownExtensions map[string]bool
var (
typesValue cue.Value
fileForExt map[string]*build.File
fileForCUE *build.File
)

var typesInit = sync.OnceFunc(func() {
ctx := cuecontext.New()
typesValue = ctx.CompileString(typesCUE, cue.Filename("types.cue"))
if err := typesValue.Err(); err != nil {
panic(err)
}
if err := typesValue.LookupPath(cue.MakePath(cue.Str("knownExtensions"))).Decode(&knownExtensions); err != nil {
// Reading a file in input mode with a non-explicit scope is a very
// common operation, so cache the build.File value for all
// the known file extensions.
if err := typesValue.LookupPath(cue.MakePath(cue.Str("fileForExtVanilla"))).Decode(&fileForExt); err != nil {
panic(err)
}
fileForCUE = fileForExt[".cue"]
// Check invariants assumed by FromFile
if fileForCUE.Form != "" || fileForCUE.Interpretation != "" || fileForCUE.Encoding != build.CUE {
panic(fmt.Errorf("unexpected value for CUE file type: %#v", fileForCUE))
}
})

0 comments on commit fed43b0

Please sign in to comment.