1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-05 08:31:00 +01:00

Merge pull request #14540 from lovesegfault/pre-compute-outputgraph

perf(libstore/derivation-builder): pre-compute outputGraph for linear complexity
This commit is contained in:
John Ericson 2025-11-29 21:46:21 +00:00 committed by GitHub
commit 01dbbc926f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 57 additions and 41 deletions

View file

@ -989,10 +989,10 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
error if a cycle is detected and roll back the error if a cycle is detected and roll back the
transaction. Cycles can only occur when a derivation transaction. Cycles can only occur when a derivation
has multiple outputs. */ has multiple outputs. */
auto topoSortResult = topoSort(paths, {[&](const StorePath & path) { auto topoSortResult = topoSort(paths, [&](const StorePath & path) {
auto i = infos.find(path); auto i = infos.find(path);
return i == infos.end() ? StorePathSet() : i->second.references; return i == infos.end() ? StorePathSet() : i->second.references;
}}); });
std::visit( std::visit(
overloaded{ overloaded{

View file

@ -313,13 +313,13 @@ MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
StorePaths Store::topoSortPaths(const StorePathSet & paths) StorePaths Store::topoSortPaths(const StorePathSet & paths)
{ {
auto result = topoSort(paths, {[&](const StorePath & path) { auto result = topoSort(paths, [&](const StorePath & path) {
try { try {
return queryPathInfo(path)->references; return queryPathInfo(path)->references;
} catch (InvalidPath &) { } catch (InvalidPath &) {
return StorePathSet(); return StorePathSet();
} }
}}); });
return std::visit( return std::visit(
overloaded{ overloaded{

View file

@ -1396,8 +1396,18 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
struct PerhapsNeedToRegister struct PerhapsNeedToRegister
{ {
StorePathSet refs; StorePathSet refs;
/**
* References to other outputs. Built by looking up in
* `scratchOutputsInverse`.
*/
StringSet otherOutputs;
}; };
/* inverse map of scratchOutputs for efficient lookup */
std::map<StorePath, std::string> scratchOutputsInverse;
for (auto & [outputName, path] : scratchOutputs)
scratchOutputsInverse.insert_or_assign(path, outputName);
std::map<std::string, std::variant<AlreadyRegistered, PerhapsNeedToRegister>> outputReferencesIfUnregistered; std::map<std::string, std::variant<AlreadyRegistered, PerhapsNeedToRegister>> outputReferencesIfUnregistered;
std::map<std::string, struct stat> outputStats; std::map<std::string, struct stat> outputStats;
for (auto & [outputName, _] : drv.outputs) { for (auto & [outputName, _] : drv.outputs) {
@ -1466,12 +1476,24 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
references = scanForReferences(blank, actualPath, referenceablePaths); references = scanForReferences(blank, actualPath, referenceablePaths);
} }
outputReferencesIfUnregistered.insert_or_assign(outputName, PerhapsNeedToRegister{.refs = references}); StringSet referencedOutputs;
for (auto & r : references)
if (auto * o = get(scratchOutputsInverse, r))
referencedOutputs.insert(*o);
outputReferencesIfUnregistered.insert_or_assign(
outputName,
PerhapsNeedToRegister{
.refs = references,
.otherOutputs = referencedOutputs,
});
outputStats.insert_or_assign(outputName, std::move(st)); outputStats.insert_or_assign(outputName, std::move(st));
} }
auto topoSortResult = topoSort(outputsToSort, {[&](const std::string & name) { StringSet emptySet;
auto orifu = get(outputReferencesIfUnregistered, name);
auto topoSortResult = topoSort(outputsToSort, [&](const std::string & name) -> const StringSet & {
auto * orifu = get(outputReferencesIfUnregistered, name);
if (!orifu) if (!orifu)
throw BuildError( throw BuildError(
BuildResult::Failure::OutputRejected, BuildResult::Failure::OutputRejected,
@ -1483,19 +1505,11 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
/* Since we'll use the already installed versions of these, we /* Since we'll use the already installed versions of these, we
can treat them as leaves and ignore any references they can treat them as leaves and ignore any references they
have. */ have. */
[&](const AlreadyRegistered &) { return StringSet{}; }, [&](const AlreadyRegistered &) -> const StringSet & { return emptySet; },
[&](const PerhapsNeedToRegister & refs) { [&](const PerhapsNeedToRegister & refs) -> const StringSet & { return refs.otherOutputs; },
StringSet referencedOutputs;
/* FIXME build inverted map up front so no quadratic waste here */
for (auto & r : refs.refs)
for (auto & [o, p] : scratchOutputs)
if (r == p)
referencedOutputs.insert(o);
return referencedOutputs;
},
}, },
*orifu); *orifu);
}}); });
auto sortedOutputNames = std::visit( auto sortedOutputNames = std::visit(
overloaded{ overloaded{

View file

@ -3,6 +3,7 @@
#include "nix/util/error.hh" #include "nix/util/error.hh"
#include <variant> #include <variant>
#include <concepts>
namespace nix { namespace nix {
@ -16,8 +17,9 @@ struct Cycle
template<typename T> template<typename T>
using TopoSortResult = std::variant<std::vector<T>, Cycle<T>>; using TopoSortResult = std::variant<std::vector<T>, Cycle<T>>;
template<typename T, typename Compare> template<typename T, typename Compare, std::invocable<const T &> F>
TopoSortResult<T> topoSort(std::set<T, Compare> items, std::function<std::set<T, Compare>(const T &)> getChildren) requires std::same_as<std::remove_cvref_t<std::invoke_result_t<F, const T &>>, std::set<T, Compare>>
TopoSortResult<T> topoSort(std::set<T, Compare> items, F && getChildren)
{ {
std::vector<T> sorted; std::vector<T> sorted;
decltype(items) visited, parents; decltype(items) visited, parents;
@ -34,7 +36,7 @@ TopoSortResult<T> topoSort(std::set<T, Compare> items, std::function<std::set<T,
} }
parents.insert(path); parents.insert(path);
auto references = getChildren(path); auto && references = std::invoke(getChildren, path);
for (auto & i : references) for (auto & i : references)
/* Don't traverse into items that don't exist in our starting set. */ /* Don't traverse into items that don't exist in our starting set. */