Skip to main content

num_cpus/
lib.rs

1//! A crate with utilities to determine the number of CPUs available on the
2//! current system.
3//!
4//! Sometimes the CPU will exaggerate the number of CPUs it contains, because it can use
5//! [processor tricks] to deliver increased performance when there are more threads. This
6//! crate provides methods to get both the logical and physical numbers of cores.
7//!
8//! This information can be used as a guide to how many tasks can be run in parallel.
9//! There are many properties of the system architecture that will affect parallelism,
10//! for example memory access speeds (for all the caches and RAM) and the physical
11//! architecture of the processor, so the number of CPUs should be used as a rough guide
12//! only.
13//!
14//!
15//! ## Examples
16//!
17//! Fetch the number of logical CPUs.
18//!
19//! ```
20//! let cpus = num_cpus::get();
21//! ```
22//!
23//! See [`rayon::Threadpool`] for an example of where the number of CPUs could be
24//! used when setting up parallel jobs (Where the threadpool example uses a fixed
25//! number 8, it could use the number of CPUs).
26//!
27//! [processor tricks]: https://en.wikipedia.org/wiki/Simultaneous_multithreading
28//! [`rayon::ThreadPool`]: https://docs.rs/rayon/1.*/rayon/struct.ThreadPool.html
29#![cfg_attr(test, deny(warnings))]
30#![deny(missing_docs)]
31
32#[cfg(target_os = "linux")]
33mod linux;
34#[cfg(target_os = "linux")]
35use linux::get_num_physical_cpus;
36
37/// Returns the number of available CPUs of the current system.
38///
39/// This function will get the number of logical cores. Sometimes this is different from the number
40/// of physical cores (See [Simultaneous multithreading on Wikipedia][smt]).
41///
42/// This will always return at least `1`.
43///
44/// # Examples
45///
46/// ```
47/// let cpus = num_cpus::get();
48/// if cpus > 1 {
49///     println!("We are on a multicore system with {} CPUs", cpus);
50/// } else {
51///     println!("We are on a single core system");
52/// }
53/// ```
54///
55/// # Note
56///
57/// This delegates to [`std::thread::available_parallelism`]. On Linux and other supported
58/// platforms that accounts for the current thread's CPU affinity mask as well as CPU quotas
59/// enforced by cgroups, so it may report fewer CPUs than are physically present.
60///
61/// [smt]: https://en.wikipedia.org/wiki/Simultaneous_multithreading
62#[inline]
63pub fn get() -> usize {
64    get_num_cpus()
65}
66
67/// Returns the number of logical CPUs available to the current thread.
68///
69/// This is the standard library's [`available_parallelism`], falling back to `1` when the
70/// platform does not report a value.
71///
72/// [`available_parallelism`]: std::thread::available_parallelism
73#[inline]
74fn get_num_cpus() -> usize {
75    std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1)
76}
77
78/// Returns the number of physical cores of the current system.
79///
80/// This will always return at least `1`.
81///
82/// # Note
83///
84/// Physical count is supported only on Linux, macOS, OpenBSD and AIX platforms.
85/// On other platforms, or if the physical count fails on supported platforms,
86/// this function returns the same as [`get()`], which is the number of logical
87/// CPUS.
88///
89/// # Examples
90///
91/// ```
92/// let logical_cpus = num_cpus::get();
93/// let physical_cpus = num_cpus::get_physical();
94/// if logical_cpus > physical_cpus {
95///     println!("We have simultaneous multithreading with about {:.2} \
96///               logical cores to 1 physical core.",
97///               (logical_cpus as f64) / (physical_cpus as f64));
98/// } else if logical_cpus == physical_cpus {
99///     println!("Either we don't have simultaneous multithreading, or our \
100///               system doesn't support getting the number of physical CPUs.");
101/// } else {
102///     println!("We have less logical CPUs than physical CPUs, maybe we only have access to \
103///               some of the CPUs on our system.");
104/// }
105/// ```
106///
107/// [`get()`]: fn.get.html
108#[inline]
109pub fn get_physical() -> usize {
110    get_num_physical_cpus()
111}
112
113#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "openbsd", target_os = "aix")))]
114#[inline]
115fn get_num_physical_cpus() -> usize {
116    // Not implemented, fall back
117    get_num_cpus()
118}
119
120#[cfg(target_os = "openbsd")]
121fn get_num_physical_cpus() -> usize {
122    use std::ptr;
123
124    let mut cpus: libc::c_uint = 0;
125    let mut cpus_size = std::mem::size_of_val(&cpus);
126    let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
127    let rc: libc::c_int;
128
129    unsafe {
130        rc = libc::sysctl(
131            mib.as_mut_ptr(),
132            2,
133            &mut cpus as *mut _ as *mut _,
134            &mut cpus_size as *mut _ as *mut _,
135            ptr::null_mut(),
136            0,
137        );
138    }
139    if rc < 0 {
140        cpus = 1;
141    }
142    cpus as usize
143}
144
145#[cfg(target_os = "macos")]
146fn get_num_physical_cpus() -> usize {
147    use std::{ffi::CStr, ptr};
148
149    let mut cpus: i32 = 0;
150    let mut cpus_size = std::mem::size_of_val(&cpus);
151
152    let sysctl_name = CStr::from_bytes_with_nul(b"hw.physicalcpu\0").expect("byte literal is missing NUL");
153
154    unsafe {
155        if 0 != libc::sysctlbyname(
156            sysctl_name.as_ptr(),
157            &mut cpus as *mut _ as *mut _,
158            &mut cpus_size as *mut _ as *mut _,
159            ptr::null_mut(),
160            0,
161        ) {
162            return get_num_cpus();
163        }
164    }
165    cpus as usize
166}
167
168#[cfg(target_os = "aix")]
169fn get_num_physical_cpus() -> usize {
170    match get_smt_threads_aix() {
171        Some(num) => get_num_cpus() / num,
172        None => get_num_cpus(),
173    }
174}
175
176#[cfg(target_os = "aix")]
177fn get_smt_threads_aix() -> Option<usize> {
178    let smt = unsafe { libc::getsystemcfg(libc::SC_SMT_TC) };
179    if smt == u64::MAX {
180        return None;
181    }
182    Some(smt as usize)
183}
184
185#[cfg(test)]
186mod tests {
187    fn env_var(name: &'static str) -> Option<usize> {
188        ::std::env::var(name).ok().map(|val| val.parse().unwrap())
189    }
190
191    #[test]
192    fn test_get() {
193        let num = super::get();
194        if let Some(n) = env_var("NUM_CPUS_TEST_GET") {
195            assert_eq!(num, n);
196        } else {
197            assert!(num > 0);
198            assert!(num < 236_451);
199        }
200    }
201
202    #[test]
203    fn test_get_physical() {
204        let num = super::get_physical();
205        if let Some(n) = env_var("NUM_CPUS_TEST_GET_PHYSICAL") {
206            assert_eq!(num, n);
207        } else {
208            assert!(num > 0);
209            assert!(num < 236_451);
210        }
211    }
212}