moirai_core/executor/parallelism.rs
1//! Logical parallelism derivation, cached for the process lifetime.
2
3/// Logical processors this process should parallelize across.
4///
5/// Derived once and cached for the process lifetime. The topology cannot
6/// change while the process runs, and deriving it is not cheap: a
7/// `CpuTopology::detect()` call measures 9,935 ns and 77 allocations totalling
8/// 16,480 bytes on a 24-processor host, because it materializes the whole
9/// NUMA and cache-level description to read one count. Callers that need a
10/// worker count per operation must not pay that, so this is the one place the
11/// derivation happens.
12///
13/// `themis` reports the machine's logical processors; `available_parallelism`
14/// is the fallback when no topology is available.
15#[must_use]
16pub fn logical_parallelism() -> usize {
17 #[cfg(feature = "std")]
18 {
19 static CACHED: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
20 *CACHED.get_or_init(detect_logical_parallelism)
21 }
22 #[cfg(not(feature = "std"))]
23 {
24 detect_logical_parallelism()
25 }
26}
27
28fn detect_logical_parallelism() -> usize {
29 #[cfg(feature = "std")]
30 {
31 themis::CpuTopology::detect()
32 .map(|topology| topology.logical_processors())
33 .or_else(|| {
34 std::thread::available_parallelism()
35 .ok()
36 .map(std::num::NonZeroUsize::get)
37 })
38 .unwrap_or(1)
39 .max(1)
40 }
41 #[cfg(not(feature = "std"))]
42 {
43 4 // Reasonable default for no_std
44 }
45}