Speed up evaluations across build with a persistent evaluation cache
- Dominant language
- C#
- Stars
- 5.5k
- Forks
- 1.5k
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 141
Description
## Background
Every interaction with MSBuild today has two phases:
* evaluation, where we build a set of initial models of the 'thing to be built', including following Imports, evaluating static conditions, and building the list of known Targets that can be executed
* execution, where we iteratively build a given set of Target(s) on the thing(s) to be built, which may involve running other Targets via dependencies, and evaluating other projects as part of calling Target(s) on them
The cost of evaluation is borne even by tools that do not need to build - only get some state about the project (via `-getProperty`, `-getItem`, and other means).
When an evaluation is performed, it is cached for the lifetime of a _single build invocation_ (for CLI use cases - API-based users may cache for longer via the use of ProjectCollections). This means the cost of evaluation is repeatedly paid on every build invocation, even if the inputs to the build haven't changed at all. We have _billions_ of invocations each month, and many, many more uses of `dotnet` CLI commands (and commands that make use of the Roslyn MSBuild Workspace, and more API-based consumers) that perform evaluations via APIs that aren't directly part of build invocations.
Improving the performance of the evaluation phase would help hot inner loops more than it would help end-to-end CI times, but there is some potential for shared benefit for cold CI scenarios as well - eval is something that must be done even for proposed stricter build systems like BuildXL or Precompute, so anything that makes it faster improves the responsiveness of these tools as well. In addition, tools like `dotnet format` today are hard to optimize because their architecture requires performing evaluations of the entire workspace, which can be prohibitively expensive for large workspaces.
## Proposal
Therefore, we should build a mechanism for sharing evaluation results across builds/consumers in some way. Along the way, we will likely need to answer questions about how to invalidate stale evaluations, and ways we can optimize the cost of creating new optimizations (e.g. via structural sharing, symbolic representations, or other strategies).
### Potential Phases
Today, the cache of 'project configurations' is maintained across a given build - the scheduler is aware of where particular configurations live. This cache is cleared on each build. We should consider extending its lifetime across the duration of the MSBuild Server Node. Extending the lifetime may require tracking some state about the sources/inputs of the evaluation to help us invalidate it correctly even at this early stage.
Once in-memory, cross-build evaluation caching is understood, we could progress to persistent local evaluation caches, so that evaluations from different build invocations on the same host may be able to be reused. This will likely introduce other problems to solve, like contention on writing the persistent state, invalidation of the evaluation inputs when there's not a persistent Server node to track changes to files, and so on.
Once persistent, file-based caching on the same host is understood, we could progress towards sharing these persistent state files across hosts - this would likely flesh out many issues related to how _specific_ the evaluations are today. Things like absolute file paths and casing differences on Property/Item values would lead to invalidation unless specifically accounted for. This step would allow treating the evaluation cache like a build artifact itself, and folks could potentially hook this portion up to remote stores, use CI provider artifacts/store to store/fetch it, or even build tooling on top of this file for accurate project management/information purposes.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.