From fed43b04babeac63d69b5a14529a20b69556b7a4 Mon Sep 17 00:00:00 2001 From: Roger Peppe Date: Wed, 10 Jul 2024 11:12:40 +0100 Subject: [PATCH] internal/filetypes: speed up common case 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 Change-Id: I46f8c823cca08a44a09dece8d5e9a3df55bcf928 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1197557 Reviewed-by: Paul Jolly TryBot-Result: CUEcueckoo Unity-Result: CUE porcuepine --- internal/filetypes/filetypes.go | 25 ++++++++++++++----------- internal/filetypes/types.cue | 20 ++++++++++---------- internal/filetypes/types.go | 19 ++++++++++++++++--- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/internal/filetypes/filetypes.go b/internal/filetypes/filetypes.go index 99a1272812b..f9b64841fec 100644 --- a/internal/filetypes/filetypes.go +++ b/internal/filetypes/filetypes.go @@ -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, @@ -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 } @@ -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 { diff --git a/internal/filetypes/types.cue b/internal/filetypes/types.cue index 9ebd42397b7..ff5eaa93ba3 100644 --- a/internal/filetypes/types.cue +++ b/internal/filetypes/types.cue @@ -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 @@ -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]: { diff --git a/internal/filetypes/types.go b/internal/filetypes/types.go index 21981b7eb04..22d390c6bea 100644 --- a/internal/filetypes/types.go +++ b/internal/filetypes/types.go @@ -16,17 +16,22 @@ 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() @@ -34,7 +39,15 @@ var typesInit = sync.OnceFunc(func() { 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)) + } })