Skip to content

Speed up resolution - #61

Merged
ericmj merged 5 commits into
mainfrom
speed-up-resolution
Oct 14, 2026
Merged

ericmj merged 5 commits into
mainfrom
speed-up-resolution

Conversation

@ericmj

@ericmj ericmj commented Oct 14, 2026

Copy link
Copy Markdown
Member

These make PackageLister do less work per decision, most of all in projects with many dependencies. The counts below come from profiling Hex's offline mix deps.get --check-locked on a copy of hexpm's mix.exs and mix.lock (155 locked packages) at hexpm/hex@eaa8bd6, which vendors hex_solver 0.3.0.

Change Work before
Fetch each package's versions once per solve 10,006 versions/2 calls for 155 packages
Cache the allowed version count and preferred version of each package range 9,878 package range evaluations and 256,410 version checks in 156 decisions
Index root dependencies that have sub dependencies 13,720 lookups, each scanning 126 root dependencies
Load older versions' dependencies only as far as a dependency's constraint stays the same 3,689 dependencies/3 calls for 153 resolved packages

Hex vendors this branch in hexpm/hex#1272. Together with the Hex changes there, resolution in offline mix deps.update --all on the same project went from 213 ms to 22 ms (medians of 9 runs, macOS, Elixir 1.20.4 / OTP 29.1.1), and it writes the same mix.lock as before.

Registries see two differences. versions/2 is called once per package per solve, so the solver works with one list of versions per package even if the registry filters versions by time. dependencies/3 is only called for the versions the solver looks at and the neighbouring versions it compares constraints with, so a registry that warns about or rejects dependencies when loading them, like Hex, only does that for those versions. Package ranges are still evaluated in name order, so ties and the missing package error pick the same package as before.

The new PackageLister tests cover fetching versions once, loading dependencies only as far as the constraint stays the same, candidate caching and tie-breaking. The StreamData properties also pass with MAX_RUNS=20000.

Changelog entries go in with the 0.3.2 release, which hexpm/hex#1272 needs before it merges.

PackageLister looked up root dependencies with sub dependencies by
scanning the list of root dependencies for every version and dependency
query, most of which don't match any. Resolving hexpm's dependencies
made 13,720 such lookups over 126 root dependencies. Index them by repo
and package when the lister is created, keeping the first declaration
like Enum.find/2 did.
PackageLister asked the registry for a package's versions every time it
considered the package, which pick_package/2 does for every unsatisfied
package on every decision. Resolving hexpm's dependencies through Hex
made over 10,000 such calls for 155 packages, each through Hex's
registry process. Keep the result in the lister for the rest of the
solve. This also means the solver works with one list of versions per
package even if the registry filters versions by time.
pick_package/2 filters every unsatisfied package's versions by its
constraint on every decision, though most packages stay unsatisfied
with the same constraint over many decisions. Resolving hexpm's
dependencies evaluated 9,878 package ranges and 256,410 version checks
in 156 decisions. Cache the number of allowed versions and the
preferred one per repo, package and constraint for the solve. Package
ranges are still evaluated in name order, so ties and errors pick the
same package as before.
dependencies_as_incompatibilities/4 loaded the dependencies of every
version of a package to find the range of versions around the selected
one that depend on each dependency with the same constraint. Resolving
hexpm's 153 dependencies through Hex made 3,689 dependency lookups.
Walk out from the selected version instead, loading versions only until
the constraint changes, and keep loaded dependencies for the solve so
revisiting a package doesn't load them again.

Versions outside these runs are no longer loaded, so a registry that
warns about invalid requirements when loading dependencies, like Hex,
only warns about the versions the solver looked at.
Cover the package lister returning cached candidates for the same
package range, keying them on the repository and constraint, picking
the first package by name among those with the fewest allowed
versions, and returning the first package by name without versions
after other package ranges' candidates are cached.
@ericmj
ericmj merged commit 18a3ee5 into main Oct 14, 2026
20 checks passed
@ericmj
ericmj deleted the speed-up-resolution branch October 14, 2026 17: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.

1 participant