Skip to content
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

[internal] Improve Go performance by running fewer processes to analyze external packages #13128

Merged

Conversation

Eric-Arellano
Copy link
Contributor

@Eric-Arellano Eric-Arellano commented Oct 6, 2021

We were running go list -json much more frequently than necessary. We run it once per module to generate _go_external_package targets, but then we would rerun it on each package within the module. Those each often took 5-9 seconds, so this was atrocious for performance. Instead, we can simply run it once per module and leverage the engine's memoization to avoid re-running.

This refactors our external module code so that it no longer interacts at all with the Target API, which reduces coupling. We rename things like the file to external_pkg.py so that only the ExternalModuleInfo and ExternalPkgInfo types are exposed. Notably, we no longer use ResolvedGoPackage for external code, which allows us to ignore lots of irrelevant fields like test_imports.

dependencies benchmark

This requires changing this line to use Targets so that generated targets have their dependencies resolved too:

target_roots = await Get(UnexpandedTargets, Addresses, addresses)

Before (I aborted after 1 run because it was so slow):

❯ hyperfine -r 5 './pants_from_sources --no-pantsd --no-process-execution-local-cache dependencies ::'
Current estimate: 344.640 s

After:

❯ hyperfine -r 5 './pants_from_sources --no-pantsd --no-process-execution-local-cache dependencies ::'
  Time (mean ± σ):     26.501 s ±  1.537 s    [User: 29.554 s, System: 24.115 s]
  Range (min … max):   24.928 s … 28.763 s    5 runs

package benchmark

Compilation is faster now too, as we no longer need to run go list to determine the .go and .s files. It's already eagerly computed beforehand.

Before:

❯ hyperfine -r 5 './pants_from_sources --no-pantsd --no-process-execution-local-cache package ::'
Benchmark #1: ./pants_from_sources --no-pantsd --no-process-execution-local-cache package ::
  Time (mean ± σ):     54.981 s ±  1.441 s    [User: 70.577 s, System: 81.823 s]
  Range (min … max):   53.426 s … 57.188 s    5 runs

After:

❯ hyperfine -r 5 './pants_from_sources --no-pantsd --no-process-execution-local-cache package ::'
  Time (mean ± σ):     33.777 s ±  0.248 s    [User: 39.221 s, System: 39.389 s]
  Range (min … max):   33.517 s … 34.062 s    5 runs

[ci skip-rust]
[ci skip-build-wheels]

…fewer processes

# Rust tests and lints will be skipped. Delete if not intended.
[ci skip-rust]

# Building wheels and fs_util will be skipped. Delete if not intended.
[ci skip-build-wheels]
@Eric-Arellano Eric-Arellano force-pushed the go-external-package-metadata-perf branch from 0c7ec11 to 1583e7e Compare October 6, 2021 00:42
@tdyas
Copy link
Contributor

tdyas commented Oct 6, 2021

Instead, we can simply run it once per module and leverage the engine's memoization to avoid re-running.

Does this rely on pantsd running?

@Eric-Arellano
Copy link
Contributor Author

Does this rely on pantsd running?

No, even if you don't use pantsd the engine will memoize for the current run. See the benchmarks I just posted without Pantsd :D

# Rust tests and lints will be skipped. Delete if not intended.
[ci skip-rust]

# Building wheels and fs_util will be skipped. Delete if not intended.
[ci skip-build-wheels]
@Eric-Arellano Eric-Arellano changed the title [internal] Improve performance of Go dependency inference by running fewer processes [internal] Improve Go performance by running fewer processes to analyze external packages Oct 6, 2021
@tdyas
Copy link
Contributor

tdyas commented Oct 6, 2021

No, even if you don't use pantsd the engine will memoize for the current run. See the benchmarks I just posted without Pantsd :D

What about subsequent runs? Would this get cached remotely in a CAS?

@Eric-Arellano
Copy link
Contributor Author

What about subsequent runs? Would this get cached remotely in a CAS?

The go list -json Process gets cached with local cache and remote cache. The parsing of the JSON result only gets memoized, but it's also fast.

This is the same with target generation. The underlying Process required is cached to LMDB, and then the creation of the targets in-memory is memoized.

import_path_to_info = {}
for metadata in ijson.items(json_result.stdout, "", multiple_values=True):
import_path = metadata["ImportPath"]
pkg_info = ExternalPkgInfo(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExternalPkgInfo is going to need HFiles for assembly which I did not implement including in the assembly step. It would be prudent to also check if any of the other source keys are set (e.g., CgoFiles) and error if they are, so we don't try an incomplete build of an external package. This can be addressd as a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I wanted to leave off anything that we don't need until we actually consume it: keep things as simple as possible for now.

I'm fine with eagerly erroring on unsupported files though.

@Eric-Arellano Eric-Arellano merged commit 5d4ba99 into pantsbuild:main Oct 6, 2021
@Eric-Arellano Eric-Arellano deleted the go-external-package-metadata-perf branch October 6, 2021 02:19
@stuhood stuhood mentioned this pull request Oct 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants