Determinate previews parallel evaluation for selected Nix commands
Determinate Nix 3.11.1 distributes selected evaluation work across cores. The preview separates command-level parallelism from an experimental builtin that can schedule expression evaluation explicitly.
Parallel builds do not remove the cost of evaluating the configuration that describes them. Determinate Nix 3.11.1 begins addressing that earlier stage by evaluating selected Nix operations across multiple processors. The rollout is a developer preview with a defined command set, rather than a claim that every Nix invocation now evaluates in parallel.
The September 5 announcement names nix search, nix flake check, nix flake show, and nix eval --json as the initial operations. Trailblazer users receive the preview, and other users can enable it through eval-cores. A separate experimental feature controls the new builtins.parallel interface.
This interests me because my use of Nix centers on complete declarative machine configurations. The evaluator has real work to do before a system becomes something that can be built or fetched. Moving independent evaluation work across cores targets that stage directly, instead of treating every slow system command as a compilation problem.
The command determines which evaluation work is parallel
The initial scope follows operations that inspect or traverse multiple values. A flake can expose several system configurations or other outputs, while a search examines packages. The announcement describes distributing evaluation work for those supported commands and says expansion to other operations will happen incrementally.
That is an interface boundary worth preserving in the explanation. Installing this release does not establish that any arbitrary expression evaluates faster simply because several cores are idle. A supported operation needs to identify work it can schedule through the parallel evaluator, or an expression needs to use the explicit experimental mechanism.
The eval-cores setting controls evaluator threads. It is separate from the settings used to run multiple builds or to tell one builder how much internal parallelism to use. A deployment can contain evaluation work and build work at different times, so the corresponding controls describe different demands on the machine.
The announcement shows an automatic setting in Determinate's custom configuration file.
eval-cores = 0
The implementation's setting documentation says zero uses all available CPU cores. That is an automatic resource policy, not a small fixed thread count. A shared machine still needs a deliberate choice about how much evaluation concurrency is appropriate for the rest of its workload.
Explicit scheduling is an additional experiment
The implementation change introduces builtins.parallel with two arguments. Its documentation describes starting evaluation of the values in the first list in the background and returning the second argument. The source change separates scheduling that work from waiting for every item to finish before returning.
This matters because the builtin is not simply a new spelling for mapping a function over a list and collecting all results. It identifies values whose evaluation can begin early while another value becomes the returned expression. A caller needs to understand that relationship when deciding which work to make available in advance.
The implementation places the builtin behind parallel-eval. The announcement shows the corresponding configuration setting.
extra-experimental-features = parallel-eval
Enabling evaluator threads and enabling the builtin are separate decisions. A supported command can use parallel evaluation without application source depending on this new builtin. Source code that calls the builtin adds a specific dependency on an experimental language interface in this distribution.
The announcement warns that the builtin's semantics can change substantially or that it may disappear during the preview. That is a stronger qualification than ordinary implementation tuning. A source expression depending on its exact behavior accepts an interface still being designed.
Stable flakes do not make every new builtin stable
Determinate's March 3.0 announcement promised flake stability in its distribution. September's explicit warning about builtins.parallel demonstrates why that guarantee needs its stated scope. A commitment to existing flake workflows does not make every later experimental language addition immutable.
The two promises can coexist. Existing workflows can have a compatibility commitment while an opt-in builtin remains subject to revision. The flag identifies the narrower experiment, allowing users to try it without representing its current interface as a permanent extension available everywhere Nix runs.
This is also a portability consideration. The release belongs to Determinate Nix, and the builtin's availability follows that implementation and feature setting. A repository using it needs to describe that toolchain expectation. A package input lock does not cause another evaluator to gain an implementation-specific builtin.
I prefer the command-level rollout as the first adoption point. It can improve supported workflows without immediately placing an experimental scheduling call into configuration source. Explicit scheduling remains useful where the expression author knows which values should begin evaluation together, but it carries a different maintenance obligation.
Import-from-derivation can expose waiting during evaluation
The announcement identifies import-from-derivation as a case benefiting from explicit parallelization. It reports an example whose evaluation time falls from fifty seconds to ten. That is a vendor-reported result for a named test, not a general multiplier for all configurations using the idiom.
The relevant stage is still evaluation even though a derivation can be involved in producing material that evaluation imports. Scheduling several independent values can make progress while another part waits. The amount of overlap depends on the dependency relationships in the expression and the work each branch performs.
A chain in which each result depends on the previous one cannot become independent merely because a setting permits more threads. Conversely, a collection of independent outputs can offer useful work to several threads. The preview's selected commands and explicit builtin provide ways to expose that work under their documented scope.
This explains why expression structure matters to an evaluation benchmark. Counting CPU cores alone does not establish the parallel portion of the computation. The announcement's multiple-output example is informative because it identifies a workload with several system configurations, rather than presenting one timing as a property of the language in every use.
The reported benchmarks identify their conditions
Determinate reports a package search decreasing from fifteen seconds to six, with the evaluation cache disabled. It also reports a nix flake check example decreasing from twenty seconds to under five and a half, with both builds and the evaluation cache disabled. Those flags help identify what the examples measure.
Disabling builds keeps the check example focused on evaluation work. Disabling the evaluation cache prevents a previous evaluated result from answering the question before the new execution mechanism does substantial work. Neither benchmark is a measurement of rebuilding an entire machine from source.
The vendor also reports an internal flake containing several NixOS configurations decreasing from over twenty seconds to under nine. That is useful context for the intended workload, while remaining a report about that configuration. The announcement does not provide enough information to turn it into a prediction for a different repository or machine.
For my use of declarative infrastructure, the mechanism is the useful result. Supported commands can distribute independent evaluation work, and the builtin can explicitly initiate additional work under an experimental flag. The preview defines both the opportunity and the interface limits without requiring a universal speed claim.
Scheduling work can change when resources are needed
Starting work in the background moves some evaluation earlier than its ordinary demand point. That can reduce elapsed time when useful work overlaps, while also making several evaluations need CPU and memory together. A shorter elapsed interval is therefore not a claim that the computation uses fewer resources in total.
The builtin's distinction between scheduled values and its returned value makes that tradeoff visible. The expression author selects potential background work, while the rest of evaluation still determines which results are needed. Developer-preview status gives the project room to revise that scheduling interface as users explore those interactions.
Other changes belong to their own stages
The same release improves the default nix flake init template to target multiple systems. That changes the starting source a new project receives. It does not enable parallel evaluation by itself, although multi-output configurations are part of the workload the preview discusses.
The release also repairs build cancellation on macOS after a signal-handling change affected Ctrl-C. That correction concerns controlling an active build, separately from evaluating its configuration. Keeping those changes distinct makes the announcement easier to interpret when diagnosing an invocation that is slow to evaluate or difficult to stop.
September's parallel-evaluation preview is therefore a scoped implementation release. Its command list identifies automatic use, eval-cores selects concurrency, and parallel-eval gates the explicit builtin. Those separate controls let a repository describe whether it expects a faster supported command or depends on an experimental expression-level scheduling interface.