query
On this page

intersectAttrs

lib.intersectAttrs

Primop
Docs pulled from | This Revision | 26 minutes 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

Aliases

Detected Type
intersectAttrs :: AttrSet -> AttrSet -> AttrSet

Implementation

This function is implemented in c++ and is part of the native nix runtime.

src/libexpr/primops.cc:3431

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());
}

Implementation

The following is the current implementation of this function.

intersectAttrs