intersectAttrs
builtins.intersectAttrs
Primop
Docs pulled from | This Revision | about 4 hours ago
Nix manual
Takes 2 arguments
e1, e2
Return a set consisting of the attributes in the set e2 which have the same name as some attribute in e1.
Has O(n log m) time complexity, where n and m are the sizes of the smallest and largest set respectively.
Noogle detected
Detected Type
intersectAttrs :: AttrSet -> AttrSet -> AttrSet
Implementation
This function is implemented in c++ and is part of the native nix runtime.
static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
state.forceAttrs(*args[0], pos, "while evaluating the first argument passed to builtins.intersectAttrs");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.intersectAttrs");
auto & left = *args[0]->attrs();
auto & right = *args[1]->attrs();
auto attrs = state.buildBindings(std::min(left.size(), right.size()));
// The current implementation has good asymptotic complexity and is reasonably
// simple. Further optimization may be possible, but does not seem productive,
// considering the state of eval performance in 2022.
//
// I have looked for reusable and/or standard solutions and these are my
// findings:
//
// STL
// ===
// std::set_intersection is not suitable, as it only performs a simultaneous
// linear scan; not taking advantage of random access. This is O(n + m), so
// linear in the largest set, which is not acceptable for callPackage in Nixpkgs.
//
// Simultaneous scan, with alternating simple binary search
// ===
// One alternative algorithm scans the attrsets simultaneously, jumping
// forward using `lower_bound` in case of inequality. This should perform
// well on very similar sets, having a local and predictable access pattern.
// On dissimilar sets, it seems to need more comparisons than the current
// algorithm, as few consecutive attrs match. `lower_bound` could take
// advantage of the decreasing remaining search space, but this causes
// the medians to move, which can mean that they don't stay in the cache
// like they would with the current naive `find`.
//
// Double binary search
// ===
// The optimal algorithm may be "Double binary search", which doesn't
// scan at all, but rather divides both sets simultaneously.
// See "Fast Intersection Algorithms for Sorted Sequences" by Baeza-Yates et al.
// https://cs.uwaterloo.ca/~ajsaling/papers/intersection_alg_app10.pdf
// The only downsides I can think of are not having a linear access pattern
// for similar sets, and having to maintain a more intricate algorithm.
//
// Adaptive
// ===
// Finally one could run try a simultaneous scan, count misses and fall back
// to double binary search when the counter hit some threshold and/or ratio.
if (left.size() < right.size()) {
for (auto & l : left) {
auto r = right.get(l.name);
if (r)
attrs.insert(*r);
}
} else {
for (auto & r : right) {
auto l = left.get(r.name);
if (l)
attrs.insert(r);
}
}
v.mkAttrs(attrs.alreadySorted());
}