Skip to content

reduce redundant artifact, CAS, and cache-key work - #2172

Open
kotborealis wants to merge 3 commits into
apache:masterfrom
kotborealis:perf/cache-cas-optimizations
Open

reduce redundant artifact, CAS, and cache-key work#2172
kotborealis wants to merge 3 commits into
apache:masterfrom
kotborealis:perf/cache-cas-optimizations

Conversation

@kotborealis

Copy link
Copy Markdown
Contributor

This change removes redundant work from artifact creation, remote CAS directory fetching, and cache-key calculation:

  • Remove the unused recursive artifact size calculation.
  • Avoid issuing a second FetchTree request in CASCache.fetch_directory().
  • Reuse the build dependency list when calculating weak and strict cache keys.

# This code can be run multiple times until the strict key can be calculated,
# so let's ensure we only ever calculate the weak key once, even though we need
# to resolve it before we can resolve the strict key.
build_dependencies = list(self._dependencies(_Scope.BUILD))

@nathanwilliams-ct nathanwilliams-ct Aug 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just to note:

I've proposed a refactor of _calculate_cache_key over here: c3369b7#diff-7d3ea8e226c37028881ae2f47facfe404a1c84346d4238569a63b0caacfb0ea2R2360 To avoid the complicated list comprehension that goes on here, with it's mess of dynamic typing of list[tuple[str,str] | tuple[str,str,None] | tuple[str,str,str]] and hard to read logic.

Although, I still end up calling _dependencies every time _calculate_cache_key is called...


Feedback:

hmm.. Instead of storing the result of _dependencies here, it might be worth refactoring _dependencies to directly cache it's own results, so it can return the cached result after the first call, whenever it's called anywhere instead of just here. I don't think __build_dependencies or __runtime_dependencies is ever updated after an Element is first initialised in _new_from_load_element. (Although _add_build_dependency might be a problem? that would need investigating, but you could clear the cache, in that method..)

...
  __dependency_set_cache: dict[str,set[Element]] = {}

def __dependencies(...) -> ...:

  # Check if we already calculated this set of dependencies
  dependency_set_cache_key = f"{scope}, {recurse}" # (probably need to include the _dependencies 'visited' argument here too...)
  if (dependencies := self.__dependency_set_cache.get(dependency_set_cache_key)) is not None:
     for element in dependencies:
       yield element
     return
  
   # Calculate result/visited
   .... 


   # Store result for next time
   self.__build_dependencies_set[dependency_set_cache_key] = result # ( or 'visited' from the recursive visit function)

It will need some thinking, but maybe it would even be appropriate to use functools @cache annotation? https://docs.python.org/3/library/functools.html The recursive visit function might also benefit from the @cache annotation..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree that caching _dependencies() could provide a better optimization, but for this PR I kept the change deliberately narrow.
A cache would need careful invalidation when dependencies are added through.
I would prefer to handle that broader refactoring separately, with dedicated tests. The current change only targets the duplicate traversal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do you want to open a follow-up issue for this then and we can close this thread?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@kotborealis

Copy link
Copy Markdown
Contributor Author

The failed CI jobs are all failing for the same unrelated reason.

bst init still uses 2.7 as the default --min-version in src/buildstream/_frontend/cli.py:423, while the build is detected as BuildStream 2.8.0.dev0.

@nathanwilliams-ct

Copy link
Copy Markdown
Contributor

The failed CI jobs are all failing for the same unrelated reason.

bst init still uses 2.7 as the default --min-version in src/buildstream/_frontend/cli.py:423, while the build is detected as BuildStream 2.8.0.dev0.

#2175 Fixes this.

@nathanwilliams-ct

Copy link
Copy Markdown
Contributor

The failed CI jobs are all failing for the same unrelated reason.
bst init still uses 2.7 as the default --min-version in src/buildstream/_frontend/cli.py:423, while the build is detected as BuildStream 2.8.0.dev0.

#2175 Fixes this.

PR merged, you should be able to rebase the branch for a happy CI.

Artifact.cache() already stores the collected directory digest and never uses the recursive size calculation that follows it. Remove the dead accumulator and _get_size() call so artifact creation does not walk the output tree a second time.
fetch_directory() has already fetched the directory protos with FetchTree before enumerating required blobs. Tell required_blobs_for_directory() to reuse those local protos instead of issuing another FetchTree request, reducing remote CAS round trips.
Weak and strict cache keys both consume the same build dependency list. Materialize the generator once and reuse it so cache-key updates do not traverse the dependency graph twice while preserving the existing key inputs and ordering.
@kotborealis
kotborealis force-pushed the perf/cache-cas-optimizations branch from dc65f9c to 857681c Compare August 17, 2026 13:40
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