Skip to content

Make --fast meaningful again #1810

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

Closed
bconway opened this issue Mar 3, 2021 · 4 comments · Fixed by #1844
Closed

Make --fast meaningful again #1810

bconway opened this issue Mar 3, 2021 · 4 comments · Fixed by #1844
Labels
area: CLI Related to CLI area: config Related to .golangci.yml and/or cli options enhancement New feature or improvement

Comments

@bconway
Copy link

bconway commented Mar 3, 2021

Problem: When every linter claims it's fast, "fast" no longer has meaning:

$ golangci-lint linters|grep 'fast: true'|wc -l
      68

$ golangci-lint linters|grep 'fast: false'
unused (megacheck): Checks Go code for ... [fast: false, auto-fix: false]

I don't want to pick on anyone's work, but many of the linters are in fact quite slow. I wouldn't use many of them with IDE on-save functionality, anyway.

A couple ideas:

  • Get rid of the "fast" categorization and get rid of --fast. This is probably not a good proposal.
  • Use a quantitative metric to decide what is "fast." Not sure what this would look like or when/where it would be performed.

Thanks for the consideration.

@bconway bconway added the enhancement New feature or improvement label Mar 3, 2021
@ldez
Copy link
Member

ldez commented Mar 14, 2021

I created some benchmarks on the code of golangci-lint and traefik.
The script resets the cache before each command execution.

golangci-lint

prefs_golangci-lint

median: 4.29 (>= forcetypeassert)

without stylecheck, unused, staticcheck, gosimple:

prefs_golangci-lint-1

median: 4.21 (>= errcheck)

traefik

perfs_traefik

median: 12.2s (>= thelper)

without stylecheck, unused, staticcheck, gosimple:

perfs_traefik-1

median: 12.01 (>= forcetypeassert)

the script
#!/bin/bash

linters=(
	"asciicheck"
	"bodyclose"
	"cyclop"
	"deadcode"
	"depguard"
	"dogsled"
	"dupl"
	"durationcheck"
	"errcheck"
	"errorlint"
	"exhaustive"
	"exhaustivestruct"
	"exportloopref"
	"forbidigo"
	"forcetypeassert"
	"funlen"
	"gci"
	"gochecknoglobals"
	"gochecknoinits"
	"gocognit"
	"goconst"
	"gocritic"
	"gocyclo"
	"godot"
	"godox"
	"goerr113"
	"gofmt"
	"gofumpt"
	"goheader"
	"goimports"
	"golint"
	"gomnd"
	"gomodguard"
	"goprintffuncname"
	"gosec"
	"gosimple"
	"govet"
	"ifshort"
	"importas"
	"ineffassign"
#	"interfacer"
	"lll"
	"makezero"
#	"maligned"
	"misspell"
	"nakedret"
	"nestif"
	"nilerr"
	"nlreturn"
	"noctx"
	"nolintlint"
	"paralleltest"
	"prealloc"
	"predeclared"
	"revive"
	"rowserrcheck"
	"scopelint"
	"sqlclosecheck"
	"staticcheck"
	"structcheck"
	"stylecheck"
	"testpackage"
	"thelper"
	"tparallel"
	"typecheck"
	"unconvert"
	"unparam"
	"unused"
	"varcheck"
	"wastedassign"
	"whitespace"
	"wrapcheck"
	"wsl"
)

echo 'name,real,user,sys'

for item in "${linters[@]}"
do
  golangci-lint cache clean
  printf "$item,"
  { /usr/bin/time -q -f"%e,%U,%S" golangci-lint run --no-config --disable-all --print-issued-lines=false -E$item > /dev/null; } 2>&1
  echo
done

govet and staticcheck (unused, gosimple, stylecheck) contain multiple linters.

Also, the cache has a huge impact on performances: with the cache 98% of the linters are fast.

My conclusion: a simple binary state, like fast, seems not enough to describe the speed of the linters.

@bconway
Copy link
Author

bconway commented Mar 14, 2021

Wow, great work! Thanks for diving in on that. I see your point on the nuances of a fast boolean.

One nice advantage to having such a toggle is that I can minimize configuration by "using" the same set of tests in a Makefile/CI and in my IDE, with the addition of the fast flag for interactivity. Obviously "using" is in quotes here, as not all the same tests are run.

@ldez
Copy link
Member

ldez commented Mar 14, 2021

I analyzed the code related to the status fast/slow:

IsSlowLinter define if a linter is slow, but lc.LoadMode&packages.NeedTypesInfo != 0 is always false because we never use this load mode.

func (lc *Config) IsSlowLinter() bool {
return lc.IsSlow || (lc.LoadMode&packages.NeedTypesInfo != 0 && lc.LoadMode&packages.NeedDeps != 0)
}

func (lc *Config) WithLoadFiles() *Config {
lc.LoadMode |= packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles
return lc
}
func (lc *Config) WithLoadForGoAnalysis() *Config {
lc = lc.WithLoadFiles()
lc.LoadMode |= packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedTypesSizes
return lc
}

So all the linters are always considered as fast except unused because the status is hard-coded (ConsiderSlow())

linter.NewConfig(golinters.NewUnused()).
WithLoadForGoAnalysis().
WithPresets(linter.PresetUnused).
WithAlternativeNames(megacheckName).
ConsiderSlow().
WithChangeTypes().
WithURL("https://github.com/dominikh/go-tools/tree/master/unused"),

packages.NeedTypesInfo has been removed inside the PR #758 about CPU and memory optimization.

There is no "replacement" for packages.NeedTypesInfo: we cannot use another load mode because there is no real relation between the current load modes and the speed.
For example goheader or asciicheck use the WithLoadForGoAnalysis but they are fast. revive doesn't use WithLoadForGoAnalysis but it is slow.

So, first I will remove the conditions related to the load mode in IsSlowLinter because they are useless.

After I don't know what is the best way:

  • add ConsiderSlow() for all "slow" linters (it will be difficult to maintain)
  • remove the fast option
  • correlate WithLoadForGoAnalysis and IsSlow even if there is some false-positive
  • another way?
LoadMode overview
deadcode          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
errcheck          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
gosimple          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
govet             LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
ineffassign       LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
staticcheck       LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
structcheck       LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
typecheck         LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
unused            LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
varcheck          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
asciicheck        LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
bodyclose         LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
cyclop            LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
depguard          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
durationcheck     LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
errorlint         LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
exhaustive        LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
exhaustivestruct  LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
exportloopref     LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
forcetypeassert   LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
gci               LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
gocritic          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
goerr113          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
goheader          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
golint            LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
gomoddirectives   LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
gomodguard        LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
gosec             LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
importas          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
interfacer        LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
makezero          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
maligned          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
nilerr            LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
nlreturn          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
noctx             LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
paralleltest      LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
rowserrcheck      LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
sqlclosecheck     LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
stylecheck        LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
testpackage       LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
thelper           LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
tparallel         LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
unconvert         LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
unparam           LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
wastedassign      LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
wrapcheck         LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypesSizes)
dogsled           LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
dupl              LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
forbidigo         LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
funlen            LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
gochecknoglobals  LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
gochecknoinits    LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
gocognit          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
goconst           LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
gocyclo           LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
godot             LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
godox             LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
gofmt             LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
gofumpt           LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
goimports         LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
gomnd             LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
goprintffuncname  LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
ifshort           LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
lll               LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
misspell          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
nakedret          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
nestif            LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
nolintlint        LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
prealloc          LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
predeclared       LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
revive            LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
scopelint         LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
whitespace        LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)
wsl               LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles)

@bconway
Copy link
Author

bconway commented Mar 15, 2021

Thank you!

@ldez ldez added area: config Related to .golangci.yml and/or cli options area: CLI Related to CLI labels Jul 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: CLI Related to CLI area: config Related to .golangci.yml and/or cli options enhancement New feature or improvement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants