From 88f72f58820ebb0facbf1e603569c5384ec67b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Fri, 9 Apr 2021 14:12:38 +0100 Subject: [PATCH] schema/gen/go: batch file writes via a bytes.Buffer With this change, running 'go generate ./...' on the entire module while running gopls on one of its files drops gopls's CPU spinning from ~25s to well under a second. They should improve that anyway, but there's no reason for the tens of thousands of tiny FS writes on our end either. The time to run 'go generate ./...' itself is largely unaffected; it goes from ~1.2s to ~1.1s, judging by a handful of runs. --- schema/gen/go/generate.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/schema/gen/go/generate.go b/schema/gen/go/generate.go index ef9ca571..062504e1 100644 --- a/schema/gen/go/generate.go +++ b/schema/gen/go/generate.go @@ -1,9 +1,10 @@ package gengo import ( + "bytes" "fmt" "io" - "os" + "io/ioutil" "path/filepath" "sort" @@ -138,12 +139,20 @@ func Generate(pth string, pkgName string, ts schema.TypeSystem, adjCfg *AdjunctC } func withFile(filename string, fn func(io.Writer)) { - f, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) - if err != nil { + // Don't write directly to the file, as that many write syscalls can be + // expensive. Moreover, they can have a knock-on effect on daemons + // watching for file changes. gopls can easily eat CPU for many seconds + // just handling tens of thousands of file writes, for example. + // + // To alleviate both of those problems, write to a buffer first, and + // then write the resulting bytes to disk in a single go. + // A buffer is slightly better than bufio.Writer, as it gets us a bit + // more atomicity via the single write. + buf := new(bytes.Buffer) + fn(buf) + if err := ioutil.WriteFile(filename, buf.Bytes(), 0666); err != nil { panic(err) } - defer f.Close() - fn(f) } type sortableTypeNames []schema.TypeName