Skip to content

Commit 48599c6

Browse files
authored
Make fine-grained hashing. (#814)
Speed up golint: don't typecheck packages twice. Relates: #805
1 parent f134361 commit 48599c6

File tree

11 files changed

+137
-67
lines changed

11 files changed

+137
-67
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ require (
1717
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee
1818
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a
1919
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc
20-
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89
20+
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0
2121
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca
2222
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770
2323
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS
9494
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
9595
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
9696
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
97-
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89 h1:664ewjIQUXDvinFMbAsoH2V2Yvaro/X8BoYpIMTWGXI=
98-
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
97+
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA=
98+
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
9999
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
100100
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
101101
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=

internal/pkgcache/pkgcache.go

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ import (
1717
"github.com/golangci/golangci-lint/pkg/timeutils"
1818
)
1919

20+
type HashMode int
21+
22+
const (
23+
HashModeNeedOnlySelf HashMode = iota
24+
HashModeNeedDirectDeps
25+
HashModeNeedAllDeps
26+
)
27+
2028
// Cache is a per-package data cache. A cached data is invalidated when
2129
// package or it's dependencies change.
2230
type Cache struct {
@@ -46,7 +54,7 @@ func (c *Cache) Trim() {
4654
})
4755
}
4856

49-
func (c *Cache) Put(pkg *packages.Package, key string, data interface{}) error {
57+
func (c *Cache) Put(pkg *packages.Package, mode HashMode, key string, data interface{}) error {
5058
var err error
5159
buf := &bytes.Buffer{}
5260
c.sw.TrackStage("gob", func() {
@@ -59,7 +67,7 @@ func (c *Cache) Put(pkg *packages.Package, key string, data interface{}) error {
5967
var aID cache.ActionID
6068

6169
c.sw.TrackStage("key build", func() {
62-
aID, err = c.pkgActionID(pkg)
70+
aID, err = c.pkgActionID(pkg, mode)
6371
if err == nil {
6472
subkey, subkeyErr := cache.Subkey(aID, key)
6573
if subkeyErr != nil {
@@ -85,11 +93,11 @@ func (c *Cache) Put(pkg *packages.Package, key string, data interface{}) error {
8593

8694
var ErrMissing = errors.New("missing data")
8795

88-
func (c *Cache) Get(pkg *packages.Package, key string, data interface{}) error {
96+
func (c *Cache) Get(pkg *packages.Package, mode HashMode, key string, data interface{}) error {
8997
var aID cache.ActionID
9098
var err error
9199
c.sw.TrackStage("key build", func() {
92-
aID, err = c.pkgActionID(pkg)
100+
aID, err = c.pkgActionID(pkg, mode)
93101
if err == nil {
94102
subkey, subkeyErr := cache.Subkey(aID, key)
95103
if subkeyErr != nil {
@@ -125,8 +133,8 @@ func (c *Cache) Get(pkg *packages.Package, key string, data interface{}) error {
125133
return nil
126134
}
127135

128-
func (c *Cache) pkgActionID(pkg *packages.Package) (cache.ActionID, error) {
129-
hash, err := c.packageHash(pkg)
136+
func (c *Cache) pkgActionID(pkg *packages.Package, mode HashMode) (cache.ActionID, error) {
137+
hash, err := c.packageHash(pkg, mode)
130138
if err != nil {
131139
return cache.ActionID{}, errors.Wrap(err, "failed to get package hash")
132140
}
@@ -144,12 +152,19 @@ func (c *Cache) pkgActionID(pkg *packages.Package) (cache.ActionID, error) {
144152
// packageHash computes a package's hash. The hash is based on all Go
145153
// files that make up the package, as well as the hashes of imported
146154
// packages.
147-
func (c *Cache) packageHash(pkg *packages.Package) (string, error) {
148-
cachedHash, ok := c.pkgHashes.Load(pkg)
155+
func (c *Cache) packageHash(pkg *packages.Package, mode HashMode) (string, error) {
156+
type hashResults map[HashMode]string
157+
hashResI, ok := c.pkgHashes.Load(pkg)
149158
if ok {
150-
return cachedHash.(string), nil
159+
hashRes := hashResI.(hashResults)
160+
if _, ok := hashRes[mode]; !ok {
161+
return "", fmt.Errorf("no mode %d in hash result", mode)
162+
}
163+
return hashRes[mode], nil
151164
}
152165

166+
hashRes := hashResults{}
167+
153168
key, err := cache.NewHash("package hash")
154169
if err != nil {
155170
return "", errors.Wrap(err, "failed to make a hash")
@@ -158,13 +173,15 @@ func (c *Cache) packageHash(pkg *packages.Package) (string, error) {
158173
fmt.Fprintf(key, "pkgpath %s\n", pkg.PkgPath)
159174
for _, f := range pkg.CompiledGoFiles {
160175
c.ioSem <- struct{}{}
161-
h, err := cache.FileHash(f)
176+
h, fErr := cache.FileHash(f)
162177
<-c.ioSem
163-
if err != nil {
164-
return "", errors.Wrapf(err, "failed to calculate file %s hash", f)
178+
if fErr != nil {
179+
return "", errors.Wrapf(fErr, "failed to calculate file %s hash", f)
165180
}
166181
fmt.Fprintf(key, "file %s %x\n", f, h)
167182
}
183+
curSum := key.Sum()
184+
hashRes[HashModeNeedOnlySelf] = hex.EncodeToString(curSum[:])
168185

169186
imps := make([]*packages.Package, 0, len(pkg.Imports))
170187
for _, imp := range pkg.Imports {
@@ -173,20 +190,40 @@ func (c *Cache) packageHash(pkg *packages.Package) (string, error) {
173190
sort.Slice(imps, func(i, j int) bool {
174191
return imps[i].PkgPath < imps[j].PkgPath
175192
})
176-
for _, dep := range imps {
177-
if dep.PkgPath == "unsafe" {
178-
continue
179-
}
180193

181-
depHash, err := c.packageHash(dep)
182-
if err != nil {
183-
return "", errors.Wrapf(err, "failed to calculate hash for dependency %s", dep.Name)
194+
calcDepsHash := func(depMode HashMode) error {
195+
for _, dep := range imps {
196+
if dep.PkgPath == "unsafe" {
197+
continue
198+
}
199+
200+
depHash, depErr := c.packageHash(dep, depMode)
201+
if depErr != nil {
202+
return errors.Wrapf(depErr, "failed to calculate hash for dependency %s with mode %d", dep.Name, depMode)
203+
}
204+
205+
fmt.Fprintf(key, "import %s %s\n", dep.PkgPath, depHash)
184206
}
207+
return nil
208+
}
209+
210+
if err := calcDepsHash(HashModeNeedOnlySelf); err != nil {
211+
return "", err
212+
}
185213

186-
fmt.Fprintf(key, "import %s %s\n", dep.PkgPath, depHash)
214+
curSum = key.Sum()
215+
hashRes[HashModeNeedDirectDeps] = hex.EncodeToString(curSum[:])
216+
217+
if err := calcDepsHash(HashModeNeedAllDeps); err != nil {
218+
return "", err
219+
}
220+
curSum = key.Sum()
221+
hashRes[HashModeNeedAllDeps] = hex.EncodeToString(curSum[:])
222+
223+
if _, ok := hashRes[mode]; !ok {
224+
return "", fmt.Errorf("invalid mode %d", mode)
187225
}
188-
h := key.Sum()
189-
ret := hex.EncodeToString(h[:])
190-
c.pkgHashes.Store(pkg, ret)
191-
return ret, nil
226+
227+
c.pkgHashes.Store(pkg, hashRes)
228+
return hashRes[mode], nil
192229
}

pkg/exitcodes/exitcodes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ var (
3030
Code: Failure,
3131
}
3232
)
33+
34+
// 1

pkg/golinters/goanalysis/linter.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"sync/atomic"
1212
"time"
1313

14+
"github.com/golangci/golangci-lint/pkg/timeutils"
15+
16+
"github.com/golangci/golangci-lint/internal/pkgcache"
1417
"github.com/golangci/golangci-lint/pkg/logutils"
1518

1619
"golang.org/x/tools/go/packages"
@@ -332,7 +335,7 @@ func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages.
332335
}
333336

334337
atomic.AddInt32(&savedIssuesCount, int32(len(encodedIssues)))
335-
if err := lintCtx.PkgCache.Put(pkg, lintResKey, encodedIssues); err != nil {
338+
if err := lintCtx.PkgCache.Put(pkg, pkgcache.HashModeNeedAllDeps, lintResKey, encodedIssues); err != nil {
336339
lintCtx.Log.Infof("Failed to save package %s issues (%d) to cache: %s", pkg, len(pkgIssues), err)
337340
} else {
338341
issuesCacheDebugf("Saved package %s issues (%d) to cache", pkg, len(pkgIssues))
@@ -379,7 +382,7 @@ func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context,
379382
defer wg.Done()
380383
for pkg := range pkgCh {
381384
var pkgIssues []EncodingIssue
382-
err := lintCtx.PkgCache.Get(pkg, lintResKey, &pkgIssues)
385+
err := lintCtx.PkgCache.Get(pkg, pkgcache.HashModeNeedAllDeps, lintResKey, &pkgIssues)
383386
cacheRes := pkgToCacheRes[pkg]
384387
cacheRes.loadErr = err
385388
if err != nil {
@@ -430,8 +433,11 @@ func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context,
430433
}
431434

432435
func runAnalyzers(cfg runAnalyzersConfig, lintCtx *linter.Context) ([]result.Issue, error) {
433-
runner := newRunner(cfg.getName(), lintCtx.Log.Child("goanalysis"),
434-
lintCtx.PkgCache, lintCtx.LoadGuard, cfg.getLoadMode())
436+
log := lintCtx.Log.Child("goanalysis")
437+
sw := timeutils.NewStopwatch("analyzers", log)
438+
defer sw.PrintTopStages(10)
439+
440+
runner := newRunner(cfg.getName(), log, lintCtx.PkgCache, lintCtx.LoadGuard, cfg.getLoadMode(), sw)
435441

436442
pkgs := lintCtx.Packages
437443
if cfg.useOriginalPackages() {

pkg/golinters/goanalysis/runner.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828
"sync/atomic"
2929
"time"
3030

31+
"github.com/golangci/golangci-lint/pkg/timeutils"
32+
3133
"github.com/pkg/errors"
3234
"golang.org/x/tools/go/analysis"
3335
"golang.org/x/tools/go/gcexportdata"
@@ -80,16 +82,19 @@ type runner struct {
8082
loadMode LoadMode
8183
passToPkg map[*analysis.Pass]*packages.Package
8284
passToPkgGuard sync.Mutex
85+
sw *timeutils.Stopwatch
8386
}
8487

85-
func newRunner(prefix string, logger logutils.Log, pkgCache *pkgcache.Cache, loadGuard *load.Guard, loadMode LoadMode) *runner {
88+
func newRunner(prefix string, logger logutils.Log, pkgCache *pkgcache.Cache, loadGuard *load.Guard,
89+
loadMode LoadMode, sw *timeutils.Stopwatch) *runner {
8690
return &runner{
8791
prefix: prefix,
8892
log: logger,
8993
pkgCache: pkgCache,
9094
loadGuard: loadGuard,
9195
loadMode: loadMode,
9296
passToPkg: map[*analysis.Pass]*packages.Package{},
97+
sw: sw,
9398
}
9499
}
95100

@@ -481,7 +486,9 @@ func (act *action) analyzeSafe() {
481486
act.a.Name, act.pkg.Name, act.isInitialPkg, act.needAnalyzeSource, p), debug.Stack())
482487
}
483488
}()
484-
act.analyze()
489+
act.r.sw.TrackStage(act.a.Name, func() {
490+
act.analyze()
491+
})
485492
}
486493

487494
func (act *action) analyze() {
@@ -803,13 +810,13 @@ func (act *action) persistFactsToCache() error {
803810
factsCacheDebugf("Caching %d facts for package %q and analyzer %s", len(facts), act.pkg.Name, act.a.Name)
804811

805812
key := fmt.Sprintf("%s/facts", analyzer.Name)
806-
return act.r.pkgCache.Put(act.pkg, key, facts)
813+
return act.r.pkgCache.Put(act.pkg, pkgcache.HashModeNeedAllDeps, key, facts)
807814
}
808815

809816
func (act *action) loadPersistedFacts() bool {
810817
var facts []Fact
811818
key := fmt.Sprintf("%s/facts", act.a.Name)
812-
if err := act.r.pkgCache.Get(act.pkg, key, &facts); err != nil {
819+
if err := act.r.pkgCache.Get(act.pkg, pkgcache.HashModeNeedAllDeps, key, &facts); err != nil {
813820
if err != pkgcache.ErrMissing {
814821
act.r.log.Warnf("Failed to get persisted facts: %s", err)
815822
}

pkg/golinters/golint.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"go/ast"
66
"go/token"
7+
"go/types"
78
"sync"
89

910
lintAPI "github.com/golangci/lint-1"
@@ -14,9 +15,10 @@ import (
1415
"github.com/golangci/golangci-lint/pkg/result"
1516
)
1617

17-
func golintProcessPkg(minConfidence float64, files []*ast.File, fset *token.FileSet) ([]result.Issue, error) {
18+
func golintProcessPkg(minConfidence float64, files []*ast.File, fset *token.FileSet,
19+
typesPkg *types.Package, typesInfo *types.Info) ([]result.Issue, error) {
1820
l := new(lintAPI.Linter)
19-
ps, err := l.LintASTFiles(files, fset)
21+
ps, err := l.LintPkg(files, fset, typesPkg, typesInfo)
2022
if err != nil {
2123
return nil, fmt.Errorf("can't lint %d files: %s", len(files), err)
2224
}
@@ -57,7 +59,7 @@ func NewGolint() *goanalysis.Linter {
5759
nil,
5860
).WithContextSetter(func(lintCtx *linter.Context) {
5961
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
60-
res, err := golintProcessPkg(lintCtx.Settings().Golint.MinConfidence, pass.Files, pass.Fset)
62+
res, err := golintProcessPkg(lintCtx.Settings().Golint.MinConfidence, pass.Files, pass.Fset, pass.Pkg, pass.TypesInfo)
6163
if err != nil || len(res) == 0 {
6264
return nil, err
6365
}
@@ -72,5 +74,5 @@ func NewGolint() *goanalysis.Linter {
7274
}
7375
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
7476
return resIssues
75-
}).WithLoadMode(goanalysis.LoadModeSyntax)
77+
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
7678
}

pkg/lint/lintersdb/manager.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
7676
WithPresets(linter.PresetBugs).
7777
WithURL("https://github.com/kisielk/errcheck"),
7878
linter.NewConfig(golinters.NewGolint()).
79+
WithLoadForGoAnalysis().
7980
WithPresets(linter.PresetStyle).
8081
WithURL("https://github.com/golang/lint"),
8182

pkg/timeutils/stopwatch.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/golangci/golangci-lint/pkg/logutils"
1111
)
1212

13+
const noStagesText = "no stages"
14+
1315
type Stopwatch struct {
1416
name string
1517
startedAt time.Time
@@ -33,11 +35,7 @@ type stageDuration struct {
3335
d time.Duration
3436
}
3537

36-
func (s *Stopwatch) sprintStages() string {
37-
if len(s.stages) == 0 {
38-
return "no stages"
39-
}
40-
38+
func (s *Stopwatch) stageDurationsSorted() []stageDuration {
4139
stageDurations := []stageDuration{}
4240
for n, d := range s.stages {
4341
stageDurations = append(stageDurations, stageDuration{
@@ -48,6 +46,16 @@ func (s *Stopwatch) sprintStages() string {
4846
sort.Slice(stageDurations, func(i, j int) bool {
4947
return stageDurations[i].d > stageDurations[j].d
5048
})
49+
return stageDurations
50+
}
51+
52+
func (s *Stopwatch) sprintStages() string {
53+
if len(s.stages) == 0 {
54+
return noStagesText
55+
}
56+
57+
stageDurations := s.stageDurationsSorted()
58+
5159
stagesStrings := []string{}
5260
for _, s := range stageDurations {
5361
stagesStrings = append(stagesStrings, fmt.Sprintf("%s: %s", s.name, s.d))
@@ -56,6 +64,22 @@ func (s *Stopwatch) sprintStages() string {
5664
return fmt.Sprintf("stages: %s", strings.Join(stagesStrings, ", "))
5765
}
5866

67+
func (s *Stopwatch) sprintTopStages(n int) string {
68+
if len(s.stages) == 0 {
69+
return noStagesText
70+
}
71+
72+
stageDurations := s.stageDurationsSorted()
73+
74+
stagesStrings := []string{}
75+
for i := 0; i < len(stageDurations) && i < n; i++ {
76+
s := stageDurations[i]
77+
stagesStrings = append(stagesStrings, fmt.Sprintf("%s: %s", s.name, s.d))
78+
}
79+
80+
return fmt.Sprintf("top %d stages: %s", n, strings.Join(stagesStrings, ", "))
81+
}
82+
5983
func (s *Stopwatch) Print() {
6084
p := fmt.Sprintf("%s took %s", s.name, time.Since(s.startedAt))
6185
if len(s.stages) == 0 {
@@ -74,6 +98,14 @@ func (s *Stopwatch) PrintStages() {
7498
s.log.Infof("%s took %s with %s", s.name, stagesDuration, s.sprintStages())
7599
}
76100

101+
func (s *Stopwatch) PrintTopStages(n int) {
102+
var stagesDuration time.Duration
103+
for _, s := range s.stages {
104+
stagesDuration += s
105+
}
106+
s.log.Infof("%s took %s with %s", s.name, stagesDuration, s.sprintTopStages(n))
107+
}
108+
77109
func (s *Stopwatch) TrackStage(name string, f func()) {
78110
startedAt := time.Now()
79111
f()

0 commit comments

Comments
 (0)