Skip to content

Conversation

DavHau
Copy link
Member

@DavHau DavHau commented Jun 27, 2025

This change makes nix detect a machines available cores automatically whenever cores is set to 0.

So far, nix simply passed NIX_BUILD_CORES=0 whenever build-cores is set to 0. (only when cores is unset it was detecting cores automatically)

The behavior of passing NIX_BUILD_CORES=0 leads to a performance penalty when sourcing nixpkgs' generic builder's setup.sh, as setup.sh has to execute nproc. This significantly slows down sourcing of setup.sh

This affects all nixos machines since the introduction of the nix settings module a couple releases ago which sets cores to 0 be default. (It was set to 0 in nixos since at least 6 years)


Add 👍 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

@github-actions github-actions bot added the with-tests Issues related to testing. PRs with tests have some priority label Jun 27, 2025
@edolstra
Copy link
Member

The behavior of passing NIX_BUILD_CORES=0 leads to a performance penalty when sourcing nixpkgs' generic builder's setup.sh, as setup.sh has to execute nproc.

This is rather surprising, since nproc is instantaneous for me:

$ command time nproc
24
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 1716maxresident)k

This patch (288 lines) seems overly complicated for such a trivial change. I would just do something like:

env["NIX_BUILD_CORES"] = fmt("%d", settings.buildCores ? settings.buildCores : Settings::getDefaultCores());

In particular, having both a unit test and a functional test seems overkill. (Functional tests are almost always preferred over unit tests.)

@DavHau DavHau force-pushed the build-cores branch 2 times, most recently from 013a0ef to e5b7784 Compare June 27, 2025 13:43
@DavHau
Copy link
Member Author

DavHau commented Jun 27, 2025

This is rather surprising, since nproc is instantaneous for me

It is in the millisecond range, as is sourcing setup.sh. It slowed it down by roughly 10-15% if I remember correctly.
The performance thing might be minor, but I guess we want nix anyways to determine the cores on most computers, not the builder.

seems overly complicated for such a trivial change

I applied your suggestion, which removed most code. Also I removed the unit tests.

@DavHau DavHau requested a review from xokdvium June 28, 2025 16:10
@xokdvium
Copy link
Contributor

but I guess we want nix anyways to determine the cores on most computers, not the builder.

So the issue that is addressed here is that the "builder" (a.k.a. the shell script) shouldn't need to "guess" the level of parallelism?

Also the special semantics of 0 should probably be documented. But what's the reasoning for changing the default value to 0? Doesn't that behave in the exact same way as getDefaultCores() with your other changes?

@DavHau
Copy link
Member Author

DavHau commented Jun 29, 2025

So the issue that is addressed here is that the "builder" (a.k.a. the shell script) shouldn't need to "guess" the level of parallelism?

Correct!

Also the special semantics of 0 should probably be documented

Done

But what's the reasoning for changing the default value to 0? Doesn't that behave in the exact same way as getDefaultCores() with your other changes?

Yes it behaves exactly the same, I just thought that having 0 as a default is easier to follow. But I can change it back if you prefer.

@xokdvium
Copy link
Contributor

I just thought that having 0 as a default is easier to follow

It just seems that this does extra unnecessary work (getDefaultCores doesn't seem to cache the result). Though I'm not sure it would make a difference.

It also looks like getDefaultCores uses hardware_concurrency when it can't read cgroupfs. I wonder if that is correct, because:

https://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency.html

Returns the number of concurrent threads supported by the implementation. The value should be considered only a hint.

So this really doesn't correspond to the number of logical cores. Do you think this could be a problem? Since nix itself would be the sole source of truth for the level of concurrency it's important to get the default right.

@DavHau
Copy link
Member Author

DavHau commented Jun 29, 2025

So this really doesn't correspond to the number of logical cores

On my system it does correctly return the number of logical cores trying the snippet of the reference you linked:

$ cat > main.c
#include <iostream>
#include <thread>

int main()
{
    unsigned int n = std::thread::hardware_concurrency();
    std::cout << n << " concurrent threads are supported.\n";
}

$ g++ main.c && ./main
16 concurrent threads are supported.

I have 8 physical cores and 16 logical ones, so it seems correct.

@xokdvium
Copy link
Contributor

so it seems correct

Yeah, I suspect it does the right thing for most cases, though there might be some nuances.
At least libstdc++ does one of the following things:

https://github.com/gcc-mirror/gcc/blob/dd92d6acb416e138b21f00f34df54cb740e40e4c/libstdc%2B%2B-v3/src/c%2B%2B11/thread.cc#L50-L81

So I guess it's fine.

@DavHau
Copy link
Member Author

DavHau commented Jun 30, 2025

I have to correct one of my statements made in the original description above:

This affects all nixos machines since the introduction of the nix settings module a couple releases ago which sets build-cores to 0 be default.

This is not true, nixos was setting cores to 0 since quite a long time already. This means, so far only people who installed nix on foreign distros were actually using nix' builtin detection of cores.

@xokdvium
Copy link
Contributor

xokdvium commented Jul 2, 2025

Also this needs a release note.

@DavHau
Copy link
Member Author

DavHau commented Jul 3, 2025

Also this needs a release note.

Done

Copy link
Contributor

@xokdvium xokdvium left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also worth noting that the Advanced Topics documentation also mentions that 0 cores implies autodetection: https://nix.dev/manual/nix/2.29/advanced-topics/cores-vs-jobs.html

NIX_BUILD_CORES is equal to cores, unless cores equals 0, in which case NIX_BUILD_CORES will be the total number of cores in the system.

As such, this can probably be considered a bugfix?

Also briefly discussed in the team meeting:

This seems like a good first step towards ironing out the semantics of user-facing settings. Related to #11143.

This changes makes nix detect a machines available cores automatically whenever build-cores is set to 0.

So far, nix simply passed NIX_BUILD_CORES=0 whenever build-cores is set to 0. (only when build-cores is unset it was detecting cores automatically)

The behavior of passing NIX_BUILD_CORES=0 leads to a performance penalty when sourcing nixpkgs' generic builder's `setup.sh`, as setup.sh has to execute `nproc`. This significantly slows down sourcing of setup.sh
@xokdvium xokdvium merged commit b19e9ac into NixOS:master Jul 9, 2025
12 checks passed
@xokdvium
Copy link
Contributor

xokdvium commented Jul 9, 2025

@DavHau, thanks! Also would you be interested in coordinating with @emilazy to push #11143 over the line? Now that the question of the semantics of cores = 0 has been further clarified.

@roberth roberth added the backports rejected PR should not be backported (or at least not in full, or unless an overriding need arises) label Jul 30, 2025
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/nix-2-31-0-released/68465/1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backports rejected PR should not be backported (or at least not in full, or unless an overriding need arises) documentation with-tests Issues related to testing. PRs with tests have some priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants