realm / SwiftLint

A tool to enforce Swift style and conventions.
https://realm.github.io/SwiftLint
MIT License
18.67k stars 2.22k forks source link

Excluded files impact the performance of swiftlint #5018

Open sebmarchand opened 1 year ago

sebmarchand commented 1 year ago

New Issue Checklist

Describe the bug

The number of files present in the working tree of swiftlint has a huge impact on its performance even if most of these files are ignored. Here's a concrete example/repro:

echo "excluded:\n - '**/.build'" > .swiftlint.yml

- Make sure that these ignored files are actually ignored:

$ swiftlint Linting Swift files in current working directory Linting 'test.swift' (1/1) Done linting! Found 0 violations, 0 serious in 1 file.

- Benchmark swiftlint again:

➜ swiftlint_tests hyperfine --warmup 1 'swiftlint' Time (mean ± σ): 378.7 ms ± 6.1 ms [User: 229.2 ms, System: 377.6 ms] Range (min … max): 368.2 ms … 389.6 ms 10 runs


The execution time is 6x what it is when these ignored files don't exist. Using the `--use-alternative-excluding` flag brings this to 5x but it'd still be nice to completely ignore these file. Not using a glob pattern in the config improves things by another 1x.

It looks like the code is traversing the entire file tree in a few places (e.g. in the glob implementation), we could maybe make it use a recursive approach and stop when it encounters a subdirectory that is ignored (e.g. in my example it'd not look at the content of the `.build` directory at all).

### Environment

* SwiftLint version (run `swiftlint version` to be sure)?
From source
* Installation method used (Homebrew, CocoaPods, building from source, etc)?
Source
* Paste your configuration file:
See example

* Are you using [nested configurations](https://github.com/realm/SwiftLint#nested-configurations)?
No
* Which Xcode version are you using (check `xcodebuild -version`)?

$ xcodebuild -version Xcode 14.3 Build version 14E222b

keith commented 1 year ago

We hit this case as well, it seems like for us --use-alternative-excluding fixes it, but our case isn't very complex, we just try to exclude some potential nested derived data directories

included: [Modules, tools]
excluded:
  - tools/*/.build
  - tools/*/DerivedData
  - tools/ibmodulelint/tests/fixtures
  - tools/illustrationstrip/tests/fixtures
  - tools/new_feature

Interestingly based on inspecting file opens it looks like what's happening is that when we pass a file list like @/tmp/abc.txt with just ~200 files, for every file it appears that swiftlint is re-resolving all files under the matching globs, which in the case where I noticed it is ~10k files, so I guess swiftlint is attempting ~2 million file stats before even starting to lint. Maybe that isn't the only cause of the slowdown but in my case it results in ~7 minutes to lint these ~200 files, which with --use-alternative-excluding lint in ~1 second.

You can see most of the time is spent in NSFileManager image

Mordil commented 1 year ago

Our exclusion list is as follows, and we saw a 3s->150s increase in execution time

included: # paths to include during linting.
  - '*/Sources'
  - '*/Tests'
excluded: # paths to ignore during linting. Takes precedence over `included`.
  - '.build'
  - '.swiftpm'
  - 'Sources/GraphQLQueries'
  - '**/*.graphql.swift'
Mordil commented 1 year ago

There doesn't seem to be a way to actually use the --use-alertnative-excluding flag when using SwiftLint as a SwiftPM plugin?

Could this "easily" be added to the configuration file?

Mordil commented 1 year ago

With further testing, this performance impact is only seen when using globstar exclusion patterns.

If I instead list out the directories directly, SwiftLint execution drops down to the expected sub-second timing

yjdv commented 1 year ago

I'm having same issue, so you solution is to change this

included: # paths to include during linting.
  - '*/Sources'
  - '*/Tests'
excluded: # paths to ignore during linting. Takes precedence over `included`.
  - '.build'
  - '.swiftpm'
  - 'Sources/GraphQLQueries'
  - 'folder/graphql.swift'

removing the ** from the path. asking because I added few more folders to exclude and swift link plugin went from 1.6s to 23.7 seconds

sebmarchand commented 1 year ago

In my case I've been avoiding the problem by making sure that these `.build' folders don't get generated (via some settings in the Swift VSCode extension), but that's not ideal.

SimplyDanny commented 1 year ago

As a general rule one should avoid having the include and exclude patterns match too many files. They should be rather specific to avoid performance issues. I hope this restriction can be mitigated by https://github.com/realm/SwiftLint/pull/5157.