Skip to content

Commit 01dbbc9

Browse files
authored
Merge pull request #14540 from lovesegfault/pre-compute-outputgraph
perf(libstore/derivation-builder): pre-compute outputGraph for linear complexity
2 parents 0903b0a + c33b2c5 commit 01dbbc9

File tree

4 files changed

+57
-41
lines changed

4 files changed

+57
-41
lines changed

src/libstore/local-store.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -989,10 +989,10 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
989989
error if a cycle is detected and roll back the
990990
transaction. Cycles can only occur when a derivation
991991
has multiple outputs. */
992-
auto topoSortResult = topoSort(paths, {[&](const StorePath & path) {
993-
auto i = infos.find(path);
994-
return i == infos.end() ? StorePathSet() : i->second.references;
995-
}});
992+
auto topoSortResult = topoSort(paths, [&](const StorePath & path) {
993+
auto i = infos.find(path);
994+
return i == infos.end() ? StorePathSet() : i->second.references;
995+
});
996996

997997
std::visit(
998998
overloaded{

src/libstore/misc.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -313,13 +313,13 @@ MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
313313

314314
StorePaths Store::topoSortPaths(const StorePathSet & paths)
315315
{
316-
auto result = topoSort(paths, {[&](const StorePath & path) {
317-
try {
318-
return queryPathInfo(path)->references;
319-
} catch (InvalidPath &) {
320-
return StorePathSet();
321-
}
322-
}});
316+
auto result = topoSort(paths, [&](const StorePath & path) {
317+
try {
318+
return queryPathInfo(path)->references;
319+
} catch (InvalidPath &) {
320+
return StorePathSet();
321+
}
322+
});
323323

324324
return std::visit(
325325
overloaded{

src/libstore/unix/build/derivation-builder.cc

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,8 +1396,18 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
13961396
struct PerhapsNeedToRegister
13971397
{
13981398
StorePathSet refs;
1399+
/**
1400+
* References to other outputs. Built by looking up in
1401+
* `scratchOutputsInverse`.
1402+
*/
1403+
StringSet otherOutputs;
13991404
};
14001405

1406+
/* inverse map of scratchOutputs for efficient lookup */
1407+
std::map<StorePath, std::string> scratchOutputsInverse;
1408+
for (auto & [outputName, path] : scratchOutputs)
1409+
scratchOutputsInverse.insert_or_assign(path, outputName);
1410+
14011411
std::map<std::string, std::variant<AlreadyRegistered, PerhapsNeedToRegister>> outputReferencesIfUnregistered;
14021412
std::map<std::string, struct stat> outputStats;
14031413
for (auto & [outputName, _] : drv.outputs) {
@@ -1466,36 +1476,40 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
14661476
references = scanForReferences(blank, actualPath, referenceablePaths);
14671477
}
14681478

1469-
outputReferencesIfUnregistered.insert_or_assign(outputName, PerhapsNeedToRegister{.refs = references});
1479+
StringSet referencedOutputs;
1480+
for (auto & r : references)
1481+
if (auto * o = get(scratchOutputsInverse, r))
1482+
referencedOutputs.insert(*o);
1483+
1484+
outputReferencesIfUnregistered.insert_or_assign(
1485+
outputName,
1486+
PerhapsNeedToRegister{
1487+
.refs = references,
1488+
.otherOutputs = referencedOutputs,
1489+
});
14701490
outputStats.insert_or_assign(outputName, std::move(st));
14711491
}
14721492

1473-
auto topoSortResult = topoSort(outputsToSort, {[&](const std::string & name) {
1474-
auto orifu = get(outputReferencesIfUnregistered, name);
1475-
if (!orifu)
1476-
throw BuildError(
1477-
BuildResult::Failure::OutputRejected,
1478-
"no output reference for '%s' in build of '%s'",
1479-
name,
1480-
store.printStorePath(drvPath));
1481-
return std::visit(
1482-
overloaded{
1483-
/* Since we'll use the already installed versions of these, we
1484-
can treat them as leaves and ignore any references they
1485-
have. */
1486-
[&](const AlreadyRegistered &) { return StringSet{}; },
1487-
[&](const PerhapsNeedToRegister & refs) {
1488-
StringSet referencedOutputs;
1489-
/* FIXME build inverted map up front so no quadratic waste here */
1490-
for (auto & r : refs.refs)
1491-
for (auto & [o, p] : scratchOutputs)
1492-
if (r == p)
1493-
referencedOutputs.insert(o);
1494-
return referencedOutputs;
1495-
},
1496-
},
1497-
*orifu);
1498-
}});
1493+
StringSet emptySet;
1494+
1495+
auto topoSortResult = topoSort(outputsToSort, [&](const std::string & name) -> const StringSet & {
1496+
auto * orifu = get(outputReferencesIfUnregistered, name);
1497+
if (!orifu)
1498+
throw BuildError(
1499+
BuildResult::Failure::OutputRejected,
1500+
"no output reference for '%s' in build of '%s'",
1501+
name,
1502+
store.printStorePath(drvPath));
1503+
return std::visit(
1504+
overloaded{
1505+
/* Since we'll use the already installed versions of these, we
1506+
can treat them as leaves and ignore any references they
1507+
have. */
1508+
[&](const AlreadyRegistered &) -> const StringSet & { return emptySet; },
1509+
[&](const PerhapsNeedToRegister & refs) -> const StringSet & { return refs.otherOutputs; },
1510+
},
1511+
*orifu);
1512+
});
14991513

15001514
auto sortedOutputNames = std::visit(
15011515
overloaded{

src/libutil/include/nix/util/topo-sort.hh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "nix/util/error.hh"
55
#include <variant>
6+
#include <concepts>
67

78
namespace nix {
89

@@ -16,8 +17,9 @@ struct Cycle
1617
template<typename T>
1718
using TopoSortResult = std::variant<std::vector<T>, Cycle<T>>;
1819

19-
template<typename T, typename Compare>
20-
TopoSortResult<T> topoSort(std::set<T, Compare> items, std::function<std::set<T, Compare>(const T &)> getChildren)
20+
template<typename T, typename Compare, std::invocable<const T &> F>
21+
requires std::same_as<std::remove_cvref_t<std::invoke_result_t<F, const T &>>, std::set<T, Compare>>
22+
TopoSortResult<T> topoSort(std::set<T, Compare> items, F && getChildren)
2123
{
2224
std::vector<T> sorted;
2325
decltype(items) visited, parents;
@@ -34,7 +36,7 @@ TopoSortResult<T> topoSort(std::set<T, Compare> items, std::function<std::set<T,
3436
}
3537
parents.insert(path);
3638

37-
auto references = getChildren(path);
39+
auto && references = std::invoke(getChildren, path);
3840

3941
for (auto & i : references)
4042
/* Don't traverse into items that don't exist in our starting set. */

0 commit comments

Comments
 (0)