Table of Contents

Class TrialExecutorOptions

Namespace
OptunaSharp
Assembly
OptunaSharp.dll

Options for the executor that runs the trials of Study.Optimize(nJobs: …). Optuna has no counterpart; with no options set anywhere, trials run on OptunaSharp's reused dedicated threads, as before these options existed.

public class TrialExecutorOptions
Inheritance
TrialExecutorOptions
Inherited Members

Remarks

Four levels, the first that is set wins, resolved when each Optimize call starts:

  1. the executorOptions argument of that Optimize call;
  2. ExecutorOptions, a value set on the study;
  3. the DI default, DefaultExecutorOptions: the DI study factory writes it into ExecutorOptions of every study it builds, so a value set on the study afterwards replaces it;
  4. Default, the process-wide default.

Snapshot. A call reads Executor, ThreadIdleTimeout, SamplerConcurrency and BlasThreads of the resolved instance once, when it starts, and runs with those values to the end. Changing an instance, or Default, affects only calls that start afterwards.

Inheritable. The class is not sealed and its properties are virtual. A derived class may compute them; each getter is called once per Optimize call, and the values it returns are validated then, as the setters validate theirs.

Sharing. One instance may serve any number of studies and threads. Its properties are read and written atomically, but a change made while another thread starts an Optimize call can reach that call or not.

Constructors

TrialExecutorOptions()

Creates options with the defaults: DedicatedThreads, a ThreadIdleTimeout of 30 seconds, Shared and BlasThreads null (the automatic policy), the behavior of a study with no options set anywhere.

public TrialExecutorOptions()

Properties

BlasThreads

The OpenBLAS thread count for the duration of the call. null (the default) is the automatic policy: with nJobs: 1 OpenBLAS keeps its own setting (one thread per CPU unless configured otherwise, as NumPy's), and with any other nJobs it runs single-threaded while the call lasts, so parallel workers do not oversubscribe the cores. A positive value is applied for the duration of any call, nJobs: 1 included. When the call ends the count in effect before it is restored.

public virtual int? BlasThreads { get; set; }

Property Value

int?

Remarks

Why. OpenBLAS's threads are one pool for the whole process, sized for one caller. With several workers in linear algebra at once (CMA-ES's eigendecomposition, the GP stack's Cholesky) they fight for the same cores. Measured 2026-11-08 on 32 cores: 2,000 trials of a 40-dimensional CMA-ES at nJobs: 8 took 5.22 s with OpenBLAS's own 24 threads and 3.58 s pinned to one, against 4.50 s at nJobs: 1; unpinned, the parallel run was the slowest of the three. A GP study showed no difference (18.66 s against 18.68 s): its matrices are too small for OpenBLAS to thread. Optuna's own GPSampler limits its BLAS and torch threads to one for the same reason (optuna/_gp/thread_limiting.py). A parallel run is not reproducible anyway (its workers reseed from OS entropy), so pinning there costs no reproducibility; nJobs: 1 keeps byte parity with a default NumPy, whose results depend on the thread count too.

Footguns. The count is process-wide: while a call holds it, every OpenBLAS call in the process runs with it, the host's own included, and a call that holds no count (nJobs: 1 on the automatic policy) runs with whatever another call holds. Concurrent calls take the smallest count any of them asks for, and the last to end restores the original. Changing the count goes through NumSharp's OpenBLAS threading knob, which also writes OPENBLAS_NUM_THREADS into the process environment and the C runtime's copy of it; OptunaSharp removes the process-environment variable again on restore when it was not set before (the C runtime's copy keeps the restored count, the one OpenBLAS was already using). The memory OpenBLAS commits for its threads at load (measured: 788 MB of private bytes with 24 threads, 50 MB with one; the working set stays near 44 MB either way) is not returned: only OPENBLAS_NUM_THREADS=1 set before the process starts avoids it. Without a loaded OpenBLAS the setting does nothing.

Exceptions

ArgumentOutOfRangeException

The value is zero or negative; use null for the automatic policy.

Default

The process-wide default, used by every Optimize call whose argument and study leave the executor unset. Starts as a new TrialExecutorOptions: DedicatedThreads.

public static TrialExecutorOptions Default { get; set; }

Property Value

TrialExecutorOptions

Remarks

Read at the start of each Optimize call, so a new value reaches every study that never chose its own, including the study OptunaSearchCV creates inside itself. Prefer assigning a new instance over changing the shared one: a library or test that also uses this process sees either change.

Exceptions

ArgumentNullException

The value assigned is null.

Executor

The threads that run the trials: DedicatedThreads (the default) or ThreadPool. Read the footgun on ThreadPool before choosing the pool for a large nJobs.

public virtual TrialExecutorKind Executor { get; set; }

Property Value

TrialExecutorKind

Exceptions

ArgumentOutOfRangeException

The value is not a defined TrialExecutorKind.

SamplerConcurrency

How the call's nJobs workers reach the study's sampler: Shared (the default, Optuna's model: one instance, one call at a time) or PerWorker (an instance per worker, for the samplers that produce no duplicate samples that way). Ignored with nJobs: 1. Read the footguns on PerWorker before choosing it.

public virtual SamplerConcurrency SamplerConcurrency { get; set; }

Property Value

SamplerConcurrency

Exceptions

ArgumentOutOfRangeException

The value is not a defined SamplerConcurrency.

ThreadIdleTimeout

With DedicatedThreads, how long an idle worker thread waits for the next trial before it ends; default 30 seconds. A longer value keeps threads warm for an application that calls Optimize often; a shorter one frees them sooner. InfiniteTimeSpan keeps idle workers for the life of the process (they are background threads, so they never keep it alive). The thread pool ignores this value: it manages its own threads.

public virtual TimeSpan ThreadIdleTimeout { get; set; }

Property Value

TimeSpan

Remarks

Calls with the same timeout share one set of worker threads, so reuse across studies and calls keeps working; a call with a different timeout gets a set of its own.

Exceptions

ArgumentOutOfRangeException

The value is zero or negative (other than InfiniteTimeSpan), or longer than MaxValue milliseconds, the longest wait Wait(object, TimeSpan) accepts.