-
Notifications
You must be signed in to change notification settings - Fork 18k
x/tools/cmd/stringer: stringer should not use go/types #25650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
My understanding is that the plan to make things better again is #14120 (comment) . |
I suspect the stringer is using the default setup of go/types which is using a source importer (rather than an importer based on gc-compiled export data). As a consequence, each time stringer is run, it type-checks not only the file containing the stringer directive, but also all imported packages, recursively, all the way down... (That is, in fact it's surprisingly fast doing all this work... :-) It may be sufficient to simply configure types.Config with the gc importer for now (if my hunch above is correct). |
@robpike Since you have a good test case, would you mind doing the following change and see what the difference is:
I suspect this should fix it. Maybe this should be settable with a flag? (As an aside, we can probably get rid of the support for Go 1.8 now (importer18.go)). |
That change gives me an error that it can't find the filer import. I gave you a full reproducible example. It requires importing a big thing but it's not hard to reproduce. |
(@robpike Just changing the source string from "source" to "gc" (nothing else) should work w/o problem - except for a constant change there's not source change. ) Anyway, I've verified that switching from "source" to "gc" addresses the issue. On my machine, with the current version:
After applying the above change suggested above and re-installing the stringer:
which looks much more reasonable again. This just needs a decision: Should stringer require that the package it operates on compiles (and all its imports are installed)? If so, we can just make the switch. Alternatively, we could provide an additional stringer flag (say -sourceImports, or something like that) which would switch to the current behavior. We should fix this for 1.11. It's embarasingly slow as is. @robpike Comments? |
I still get this bug when I use "gc": stringer: checking package: x.go:4:2: could not import gopkg.in/src-d/go-license-detector.v2/licensedb/filer (can't find import: "gopkg.in/src-d/go-license-detector.v2/licensedb/filer") but not when I use "source". What is my mistake? It's confusing. Stringer should not care that the package compiles, only that the file parses, but changes people keep making to how things work are breaking all such assumptions. |
@robpike I think you're effectively experiencing #10249. If you Some other tools (e.g. vet) can handle this semi-magically, since they have direct support from cmd/go, and can do the install as needed, with appropriate caching. But stringer cannot, as it stands. Presumably we don't want to maintain a 'src' importer cache, since we already have a cmd/go cache. Maybe handling all of this smoothly is something that the new (planned) 'go/packages' package could do? cc @alandonovan
If that is the case, then stringer should not use go/types at all. But it has, at least as far back as #10249 (2015), so this is at least not new. It might be worth investigating to what extent we can use only go/ast. |
Also CC @myitcv, who has been thinking about stringer's use of go/types for a bit. |
I'd tend to agree with this. That's not to say therefore that stringer can't depend on Instead stringer could do as much as possible given the severity of error:
If we are comfortable that stringer should depend on |
Stringer is not in the standard repo. It could be changed for now to use go/importer.Default. The long-term (hopefully quite short-term) plan is to use golang.org/x/tools/go/packages. In fact maybe this should be one of the first programs converted. /cc @alandonovan |
Decision: @alandonovan's new go/packages will be used to fix this. |
@robpike clarifies that the bigger problem is not how long this takes but that it assumes it can type-check the source code at all. His suggestion is to go back to stringer NOT type-checking the source code. (This happened in https://go-review.googlesource.com/c/tools/+/40403 for #10249.) New decision: @robpike will update stringer NOT to use go/types. |
Revert "cmd/stringer: use source importer when available" This reverts CL 40403. The idea is to avoid type-checking and use just parsing, which should be enough for stringer. Separately reopening golang/go#10249 because the original change closed that issue, but the change is itself causing other problems as described in the discussion at golang/go#25650. This reversion restores the old behavior of stringer and will be followed with other fixes if they can be worked out. Change-Id: I8404d78da08043ede1a36b0e135a3fc7fdf6728d Reviewed-on: https://go-review.googlesource.com/121884 Reviewed-by: Ian Lance Taylor <[email protected]>
Note that it must use go/types (I've paged the stack back into my small brain) so that it can evaluate constants, which requires type checking, but perhaps it can be done efficiently. |
@robpike what about using Alan's |
Is the |
@cznic https://go-review.googlesource.com/c/tools/+/116359, though there isn't any published code yet. |
Hoping to send out working code today. It's been a fractal of details. |
Change https://golang.org/cl/122535 mentions this issue: |
Roll back my two recent changes. Stringer is now very slow again, but works in most use cases. My git foo is insufficient to do this as a revert, but it is a by-hand reversion of CLs https://go-review.googlesource.com/121884 https://go-review.googlesource.com/121995 See the issue for a long conversation about the general problem. Update golang/go#10249 Update golang/go#25650 Change-Id: I7b6ce352a4c7ebf0977883509e9d7189aaac1251 Reviewed-on: https://go-review.googlesource.com/122535 Run-TryBot: Rob Pike <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
Revert "cmd/stringer: use source importer when available" This reverts CL 40403. The idea is to avoid type-checking and use just parsing, which should be enough for stringer. Separately reopening golang/go#10249 because the original change closed that issue, but the change is itself causing other problems as described in the discussion at golang/go#25650. This reversion restores the old behavior of stringer and will be followed with other fixes if they can be worked out. Change-Id: I8404d78da08043ede1a36b0e135a3fc7fdf6728d Reviewed-on: https://go-review.googlesource.com/121884 Reviewed-by: Ian Lance Taylor <[email protected]>
Roll back my two recent changes. Stringer is now very slow again, but works in most use cases. My git foo is insufficient to do this as a revert, but it is a by-hand reversion of CLs https://go-review.googlesource.com/121884 https://go-review.googlesource.com/121995 See the issue for a long conversation about the general problem. Update golang/go#10249 Update golang/go#25650 Change-Id: I7b6ce352a4c7ebf0977883509e9d7189aaac1251 Reviewed-on: https://go-review.googlesource.com/122535 Run-TryBot: Rob Pike <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
Revert "cmd/stringer: use source importer when available" This reverts CL 40403. The idea is to avoid type-checking and use just parsing, which should be enough for stringer. Separately reopening golang/go#10249 because the original change closed that issue, but the change is itself causing other problems as described in the discussion at golang/go#25650. This reversion restores the old behavior of stringer and will be followed with other fixes if they can be worked out. Change-Id: I8404d78da08043ede1a36b0e135a3fc7fdf6728d Reviewed-on: https://go-review.googlesource.com/121884 Reviewed-by: Ian Lance Taylor <[email protected]>
Roll back my two recent changes. Stringer is now very slow again, but works in most use cases. My git foo is insufficient to do this as a revert, but it is a by-hand reversion of CLs https://go-review.googlesource.com/121884 https://go-review.googlesource.com/121995 See the issue for a long conversation about the general problem. Update golang/go#10249 Update golang/go#25650 Change-Id: I7b6ce352a4c7ebf0977883509e9d7189aaac1251 Reviewed-on: https://go-review.googlesource.com/122535 Run-TryBot: Rob Pike <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
@alandonovan Where are we with this issue? Stringer is still slow, although perhaps not unbearably so. |
Stringer continues to use go/types, and uses go/packages to obtain package information and export data for dependencies. Just now it took about 700ms to execute the Pill example in its documentation (a single source file with no imports), compared to about 60ms to compile that package, so stringer is indeed much slower than compilation. Most of that time appears to be spent in go/packages, since the |
I'm not sure exactly what's happening, but it seems like running "go list -json -compiled" is doing a fair amount of extra work, even after a build happens. Here are commands I ran on the example code, in modules mode, (I had to change the license detector import to v3 since v2's go.mod seems to be broken):
And |
What's really interesting is that
Below I show two sets of runs, each after clearing the cache. The first shows the cache count grow from 2 to 450 to 722 because the What extra work is A second observation: the real vs user times of the four commands is interesting. After a cache clear, the first command to run has a lot of work to do and has been able to parallalize much of it. But in the first pair, where the
|
Okay, testing with Following the lead from above that even the Pill example takes Here's the source we're using as the example. package painkiller
//go:generate stringer -type=Pill
type Pill int
const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
Acetaminophen = Paracetamol
) Here is the script being used to count go-build cached files and related scripts for cleaning the cache, and running go build and go list repeatedly. $ cat cache-count
find ~/Library/Caches/go-build/ -type f | wc -l
$ cat ./cache-clean
go clean -cache; cache-count
$ cat go-build
go build; cache-count
$ cat go-list-compiled-false
go list -e -json -compiled=false -export=true -deps=true -- . > /tmp/go.list.compiled.false; cache-count
$ cat go-list-compiled-true
go list -e -json -compiled=true -export=true -deps=true -- . > /tmp/go.list.compiled.true; cache-count First, a nice easy set of tests that work well. They show that when the only go file is pill.go (shown above), the $ ls *.go
pill.go
$ ./cache-clean
2
$ ./go-list-compiled-false
8
$ ./go-list-compiled-true
8
$ ./go-build
8 The fact that pill.go compiles without having run the stringer can make things confusing because testing with and without pill_string.go changes the results dramatically. $ ls *.go
pill.go
$ stringer -type=Pill
$ ls *.go
pill.go pill_string.go
$ cache-count
8
$ stringer -type=Pill
$ ls *.go
pill.go pill_string.go
$ cache-count
39 By now, you see one of the causes for confusion. Once the directory has a pill_string.go file, there is an import of "strconv" and that brings a lot more into the equation if To put it another way, if you are testing with no pill_string.go yet in the directory, running stringer is very fast, as fast as One more thing I want to show and then a question about the stringer tool and the go/packages package.
$ ./cache-clean
2
$ ls *.go
pill.go pill_string.go
$ ./go-list-compiled-false
8
$ ./go-build
8
$ ./go-list-compiled-true
34 Why does passing -compiled=true cause so many new go-build cache files to be created? I hope to answer that soon, but I suspect there's a rational reason and it will stay that way. But here's a question for the stringer and go/packages team. Should stringer have to rely on the compiled files list? I modified stringer source and go/packages source to allow a set of needs to be specified that allow -compiled to be false when invoking And one final aside. Counting file growth in the go-build cache directory is itself rife for confusion when gopls is running in the background and one is opening up new files to look around. |
A little closer to an answer for the question posed last November. For the small case of the following source file, after a call to The -compiled flag to
Currently stringer's time is spent waiting for go/packages and go/packages is waiting for /cc @rsc The separation of responsibilities between go list, go/packages and stringer isn't clear to me. I won't try to propose anything. Other conditions which may or may not be factors: How to reproduce: % cat ./reproduce
go version
go clean -cache
time go build -debug-actiongraph tmp0.actiongraph
time go list -debug-actiongraph tmp1.actiongraph -export -deps -- . > tmp1.go-list
time go list -debug-actiongraph tmp2.actiongraph -export -deps -compiled -- . > tmp2.go-list
grep "clang" tmp0.actiongraph | wc -l
grep "clang" tmp1.actiongraph | wc -l
grep "clang" tmp2.actiongraph | wc -l
% ./reproduce
go version go1.15.3 darwin/amd64
real 0m1.906s
user 0m8.020s
sys 0m2.193s
real 0m0.251s
user 0m0.292s
sys 0m0.238s
real 0m2.529s
user 0m2.327s
sys 0m1.316s
0
0
28
% ls *.go
list.go
% cat list.go
package main
import (
"gopkg.in/src-d/go-license-detector.v3/licensedb/filer"
)
func main() {
}
type Type int
const (
Other Type = iota
A
B
C
D
)
func getFiler(file string) filer.Filer {
return nil
} |
Not sure how to write this. You can ignore essentially everything I've written above over the last few days. The run time for stringer is good. The run time for gopackages is good. The run time for All will use the cached files that were created by the As long as you know to remove your deprecated GOROOT/pkg/darwin_amd64 (or similar) directory. Move GOROOT/pkg/darwin_amd64 to /tmp and Move that directory back again, and So both commands work but because they use different rules about where to look for prebuilt archives, we (or at least I) have been confused about what is triggering My clue today that my understanding was amiss, after discovering the build's -a flag, was seeing that in fact So another reason for my confusion. So if you are seeing long times when calling stringer or gopackages or go list, see if you also have this older cache directory still hanging around, and move it out of the way. I also found what appears to be another cache directory that is structured like the new one but was at GOROOT/pkg/obj/go-build. I don't know what caused my GOCACHE value to change last week, maybe a new version of go, maybe a new version of macOS. That one also was not being cleaned by the go clean command, I moved it out of the way, and it has not reappeared. |
@matloob re your comment #25650 (comment), I wonder if this is improved by your recent CLs to optimize go list? |
I know it was years ago and maybe no-one cares about the stringer times now. But since I did so much along these lines years ago and none of the main proponents had a comment at the time, my main finding would be easy to miss .. stringer was very fast when run on a new directory. Stringer became confusingly slow when it was run in a directory where previous stringer files were left behind - which would be the normal case as code evolves. Stringer didn't know how to ignore its own output files from previous runs so was doing lots of unnecessary work - because a stringer generated file imports |
There doesn't seem to be any real desire in this thread to actually avoid using go/types (see #25650 (comment)), so this issue boils down to unsatisfactory performance of the underlying golang.org/x/go/packages infrastructure, even though it is working as intended. There is indeed a high one-time cost of computing type information, but it is amortized across multiple runs. (@FrankReh I wasn't able to observe a signficant difference when removing the generated file.) Using the original example, I consistently observe times of around 5s with So I think we have two options:
I prefer the second option. I'm going to close this issue, but feel free to reopen it if you disagree. |
It takes much longer to run stringer on a program than to compile it. Stringer should be nearly instantaneous, but I suspect the new importer technology is killing performance. Something certainly is.
At tip, wiith a freshly written file, so the build cache has the dependencies but not the output of building list.go itself:
Stringer is taking almost 3 times as long to run as it takes to compile and link the binary. That's crazy. If we remove the dependency on filer, which has a pretty big set of dependencies itself, stringer runs quickly again.
The text was updated successfully, but these errors were encountered: