diff --git a/src/libexpr/parallel-eval.cc b/src/libexpr/parallel-eval.cc index a2955961e063..e0cdab93a7b8 100644 --- a/src/libexpr/parallel-eval.cc +++ b/src/libexpr/parallel-eval.cc @@ -134,10 +134,15 @@ std::vector> Executor::spawn(WorkItems && items) for (auto & item : items) { std::promise promise; futures.push_back(promise.get_future()); - [[gnu::tls_model("initial-exec")]] static thread_local std::random_device rd; + /* Note: this uses a cheap PRNG rather than std::random_device, + since the latter costs hundreds of cycles per call (RDRAND or + /dev/urandom), which adds up when spawning many work items. The + key only needs to spread items of the same priority around the + queue, not be cryptographically random. */ + [[gnu::tls_model("initial-exec")]] static thread_local std::mt19937_64 rng{std::random_device{}()}; [[gnu::tls_model("initial-exec")]] static thread_local std::uniform_int_distribution dist( 0, 1ULL << 48); - auto key = (uint64_t(item.second) << 48) | dist(rd); + auto key = (uint64_t(item.second) << 48) | dist(rng); state->queue.emplace(key, Item{.promise = std::move(promise), .work = std::move(item.first)}); } } diff --git a/src/nix/search.cc b/src/nix/search.cc index d1cebbac67a9..4d1abf24a690 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -114,6 +114,12 @@ struct CmdSearch : InstallableValueCommand, MixJSON work, std::string_view(state->symbols[attr]).find("Packages") != std::string_view::npos ? 0 : 2, [cursor2, attrPath2, visit]() { visit(*cursor2, attrPath2, false); }); + /* Spawn incrementally rather than after the whole + enumeration, so that idle worker threads can start + on the first attributes while we're still + enumerating the rest. */ + if (work.size() >= 256) + futures.spawn(std::exchange(work, {})); } futures.spawn(std::move(work)); };