Skip to content

Speed up install of environments with dev packages#27167

Merged
becker33 merged 2 commits intospack:developfrom
tmadlener:env-memoize-rebuild-specs
Nov 29, 2021
Merged

Speed up install of environments with dev packages#27167
becker33 merged 2 commits intospack:developfrom
tmadlener:env-memoize-rebuild-specs

Conversation

@tmadlener
Copy link
Copy Markdown
Contributor

I am currently experimenting with using environments as development environments, and I have an environment with roughly 50 root specs, with about 20 of them being local dev packages, i.e.

spack:
  specs: 
    - lcio
  # about 50 other packages here
  develop:
    lcio:
      path: /path/to/local/git/repo
      spec: lcio@master
    # another 20 of these

This seems to work as expected, i.e. changing a dev package will result in a rebuild of the package itself and all its dependents via spack install. The problem I ran into was that it takes quite long (order of 15 - 20 minutes) for spack to figure out which packages need to be built again. This is also the case when there were no changes at all.

I have tracked this down to the fact that Environment._spec_needs_overwrite is called over and over again with the same specs, because when figuring out which packages to rebuild, spack walks the complete DAGs of all root packages (and then recursively the DAGs of dependencies as far as I can tell). This becomes quite expensive, because for dev packages it will actually start to compare timestamps of the source files with the installation time stamp. Simply memoizing the results for a given spec avoids having to check specs multiple times in different dependency graphs.

For me the observable behavior is unchanged. I have timed a spack install with and without these changes, where all packages were up to date, i.e. only the "overhead" (figuring out which packages to build, recreating the view, etc..) but no actual build is measured. Times before:

real	19m21,477s
user	17m20,906s
sys	1m58,962s

with memoizing:

real	1m49,284s
user	1m46,653s
sys	0m2,568s

@tmadlener tmadlener force-pushed the env-memoize-rebuild-specs branch from f7cf2c4 to 0567509 Compare November 3, 2021 07:21
@bernhardkaindl
Copy link
Copy Markdown
Contributor

Initial analysis of the codecov/patch code coverage issue:

@tmadlener From looking at the report diff, the red background color of the line number and the hover text of the 🛈 symbol left to the red line number indicates that Line 1420 is not covered by tests: https://app.codecov.io/gh/spack/spack/compare/27167/diff#D1L1420
Adding a test to run with spack unit-test would be needed to make it pass.
Since the new line is in def _get_overwrite_specs() I'm assuming a test which uses spack install --overwrite should cover it, and there are appear to be some, according to grep -r overwrite lib/spack/spack/test. Maybe you have an idea why none of them appears to cover that line (assuming everything else I see is correct).

Copy link
Copy Markdown
Member

@becker33 becker33 left a comment

Choose a reason for hiding this comment

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

The line that codecov is complaining about should be covered by the test test_dev_build_rebuild_on_source_changes in lib/spack/spack/test/cmd/dev_build.py. I'm struggling to see why this change would make it uncovered. It looks like this PR also removes coverage for the rest of the _get_overwrite_specs method.


def _get_overwrite_specs(self):
# TODO: Can the cache even be populated at this point?
self._spec_needs_overwrite.cache.clear()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the only way this could be populated at this point is if the user is using spack-python and doing multiple spack installs from the same python shell.

@becker33
Copy link
Copy Markdown
Member

becker33 commented Nov 8, 2021

If we're not able to figure out why the codecov is dropping, another option would be to avoid memoizing and just precompute the set of all specs we need to check. Something like:

def _get_overwrite_specs(self):
    specs_to_check = set()
    for dag_hash in self.concretized_order:
        root_spec = self.specs_by_hash[dag_hash]
        specs_to_check.update(root_spec.traverse(root=True))
    return [s for s in specs_to_check if self._spec_needs_overwrite(s)]

Since specs are appropriately hashable, that should eliminate the duplicate work that memoizing eliminated.

@tmadlener
Copy link
Copy Markdown
Contributor Author

If we're not able to figure out why the codecov is dropping, another option would be to avoid memoizing and just precompute the set of all specs we need to check.

That sounds like an overall nicer solution in any case in my opinion. I will give that a go and see if it works.

@tmadlener
Copy link
Copy Markdown
Contributor Author

Ah, I just realized why I went with the memoization. _spec_needs_overwrite calls itself as well to figure out if dependent packages need to be rebuilt because (dev) dependencies have changed. Only having the set that collects all specs upfront doesn't help because only the first layer of dependency tree walking is removed, but below that nothing changes.

I have not been able yet to come up with some clever changes to _spec_needs_overwrite yet, that would work without the memoization and still respect the dependencies.

@tmadlener
Copy link
Copy Markdown
Contributor Author

tmadlener commented Nov 22, 2021

I found an alternative solution that avoids memoizing internal function calls. In my testing it seems to be only slightly slower compared to the memoized version (order of roughly 10 %).

The basic principle now is:

  • Get a unique list of all specs in the environment (i.e. root specs and their dependencies)
  • Check which ones of those are dev builds and have changed w.r.t. to the installed version
  • Use the list of changed dev build specs as input to the _spec_needs_overwrite function

This still does a bit more work than it would need to, but at least it skips the costly part of repeatedly going to the file system to check time stamps for the same files over and over again, as that is done only once now per each dev build package.

@tmadlener tmadlener force-pushed the env-memoize-rebuild-specs branch from 1ecf831 to 069b898 Compare November 22, 2021 17:11
@bernhardkaindl
Copy link
Copy Markdown
Contributor

@tmadlener, the CI Pipeline failure seems to be from:

Error: Pipeline generation failed due to the presence of the following specs that are known to be broken in develop:
[email protected]%[email protected]cuda+examples+exercisesipo+openmprocm+sharedtests

raja might have been fixed by: 6f8fd5b

Just to be one the safe side: Maybe could to rebase this PR to the current develop? (Just rerunning the CI could work too, but maybe it's a good idea to also rebase it)

@tmadlener tmadlener force-pushed the env-memoize-rebuild-specs branch from 069b898 to 9853568 Compare November 29, 2021 09:06
Copy link
Copy Markdown
Member

@becker33 becker33 left a comment

Choose a reason for hiding this comment

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

This looks great

@becker33 becker33 merged commit ecb5887 into spack:develop Nov 29, 2021
matz-e pushed a commit to BlueBrain/spack that referenced this pull request Dec 15, 2021
* only check file modification times once per dev package
matz-e added a commit to BlueBrain/spack that referenced this pull request Dec 15, 2021
* only check file modification times once per dev package

Co-authored-by: Thomas Madlener <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants