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#![allow(non_snake_case)]
32
33#[cfg(not(windows))]
34extern crate libc;
35
36#[cfg(target_os = "hermit")]
37extern crate hermit_abi;
38
39#[cfg(target_os = "linux")]
40mod linux;
41#[cfg(target_os = "linux")]
42use linux::{get_num_cpus, get_num_physical_cpus};
43
44/// Returns the number of available CPUs of the current system.
45///
46/// This function will get the number of logical cores. Sometimes this is different from the number
47/// of physical cores (See [Simultaneous multithreading on Wikipedia][smt]).
48///
49/// This will always return at least `1`.
50///
51/// # Examples
52///
53/// ```
54/// let cpus = num_cpus::get();
55/// if cpus > 1 {
56///     println!("We are on a multicore system with {} CPUs", cpus);
57/// } else {
58///     println!("We are on a single core system");
59/// }
60/// ```
61///
62/// # Note
63///
64/// This will check [sched affinity] on Linux, showing a lower number of CPUs if the current
65/// thread does not have access to all the computer's CPUs.
66///
67/// This will also check [cgroups], frequently used in containers to constrain CPU usage.
68///
69/// [smt]: https://en.wikipedia.org/wiki/Simultaneous_multithreading
70/// [sched affinity]: http://www.gnu.org/software/libc/manual/html_node/CPU-Affinity.html
71/// [cgroups]: https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
72#[inline]
73pub fn get() -> usize {
74    get_num_cpus()
75}
76
77/// Returns the number of physical cores of the current system.
78///
79/// This will always return at least `1`.
80///
81/// # Note
82///
83/// Physical count is supported only on Linux, mac OS and Windows platforms.
84/// On other platforms, or if the physical count fails on supported platforms,
85/// this function returns the same as [`get()`], which is the number of logical
86/// CPUS.
87///
88/// # Examples
89///
90/// ```
91/// let logical_cpus = num_cpus::get();
92/// let physical_cpus = num_cpus::get_physical();
93/// if logical_cpus > physical_cpus {
94///     println!("We have simultaneous multithreading with about {:.2} \
95///               logical cores to 1 physical core.",
96///               (logical_cpus as f64) / (physical_cpus as f64));
97/// } else if logical_cpus == physical_cpus {
98///     println!("Either we don't have simultaneous multithreading, or our \
99///               system doesn't support getting the number of physical CPUs.");
100/// } else {
101///     println!("We have less logical CPUs than physical CPUs, maybe we only have access to \
102///               some of the CPUs on our system.");
103/// }
104/// ```
105///
106/// [`get()`]: fn.get.html
107#[inline]
108pub fn get_physical() -> usize {
109    get_num_physical_cpus()
110}
111
112#[cfg(not(any(
113    target_os = "linux",
114    target_os = "windows",
115    target_os = "macos",
116    target_os = "openbsd",
117    target_os = "aix"
118)))]
119#[inline]
120fn get_num_physical_cpus() -> usize {
121    // Not implemented, fall back
122    get_num_cpus()
123}
124
125#[cfg(target_os = "windows")]
126fn get_num_physical_cpus() -> usize {
127    match get_num_physical_cpus_windows() {
128        Some(num) => num,
129        None => get_num_cpus(),
130    }
131}
132
133#[cfg(target_os = "windows")]
134fn get_num_physical_cpus_windows() -> Option<usize> {
135    // Inspired by https://msdn.microsoft.com/en-us/library/ms683194
136
137    use std::{mem, ptr};
138
139    #[allow(non_upper_case_globals)]
140    const RelationProcessorCore: u32 = 0;
141
142    #[repr(C)]
143    #[allow(non_camel_case_types)]
144    struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION {
145        mask: usize,
146        relationship: u32,
147        _unused: [u64; 2],
148    }
149
150    extern "system" {
151        fn GetLogicalProcessorInformation(info: *mut SYSTEM_LOGICAL_PROCESSOR_INFORMATION, length: &mut u32) -> u32;
152    }
153
154    // First we need to determine how much space to reserve.
155
156    // The required size of the buffer, in bytes.
157    let mut needed_size = 0;
158
159    unsafe {
160        GetLogicalProcessorInformation(ptr::null_mut(), &mut needed_size);
161    }
162
163    let struct_size = mem::size_of::<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>() as u32;
164
165    // Could be 0, or some other bogus size.
166    if needed_size == 0 || needed_size < struct_size || needed_size % struct_size != 0 {
167        return None;
168    }
169
170    let count = needed_size / struct_size;
171
172    // Allocate some memory where we will store the processor info.
173    let mut buf = Vec::with_capacity(count as usize);
174
175    let result;
176
177    unsafe {
178        result = GetLogicalProcessorInformation(buf.as_mut_ptr(), &mut needed_size);
179    }
180
181    // Failed for any reason.
182    if result == 0 {
183        return None;
184    }
185
186    let count = needed_size / struct_size;
187
188    unsafe {
189        buf.set_len(count as usize);
190    }
191
192    let phys_proc_count = buf
193        .iter()
194        // Only interested in processor packages (physical processors.)
195        .filter(|proc_info| proc_info.relationship == RelationProcessorCore)
196        .count();
197
198    if phys_proc_count == 0 {
199        None
200    } else {
201        Some(phys_proc_count)
202    }
203}
204
205#[cfg(windows)]
206fn get_num_cpus() -> usize {
207    #[repr(C)]
208    struct SYSTEM_INFO {
209        wProcessorArchitecture: u16,
210        wReserved: u16,
211        dwPageSize: u32,
212        lpMinimumApplicationAddress: *mut u8,
213        lpMaximumApplicationAddress: *mut u8,
214        dwActiveProcessorMask: *mut u8,
215        dwNumberOfProcessors: u32,
216        dwProcessorType: u32,
217        dwAllocationGranularity: u32,
218        wProcessorLevel: u16,
219        wProcessorRevision: u16,
220    }
221
222    extern "system" {
223        fn GetSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
224    }
225
226    unsafe {
227        let mut sysinfo: SYSTEM_INFO = std::mem::zeroed();
228        GetSystemInfo(&mut sysinfo);
229        sysinfo.dwNumberOfProcessors as usize
230    }
231}
232
233#[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd"))]
234fn get_num_cpus() -> usize {
235    use std::ptr;
236
237    let mut cpus: libc::c_uint = 0;
238    let mut cpus_size = std::mem::size_of_val(&cpus);
239
240    unsafe {
241        cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint;
242    }
243    if cpus < 1 {
244        let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
245        unsafe {
246            libc::sysctl(
247                mib.as_mut_ptr(),
248                2,
249                &mut cpus as *mut _ as *mut _,
250                &mut cpus_size as *mut _ as *mut _,
251                ptr::null_mut(),
252                0,
253            );
254        }
255        if cpus < 1 {
256            cpus = 1;
257        }
258    }
259    cpus as usize
260}
261
262#[cfg(target_os = "openbsd")]
263fn get_num_cpus() -> usize {
264    use std::ptr;
265
266    let mut cpus: libc::c_uint = 0;
267    let mut cpus_size = std::mem::size_of_val(&cpus);
268    let mut mib = [libc::CTL_HW, libc::HW_NCPUONLINE, 0, 0];
269    let rc: libc::c_int;
270
271    unsafe {
272        rc = libc::sysctl(
273            mib.as_mut_ptr(),
274            2,
275            &mut cpus as *mut _ as *mut _,
276            &mut cpus_size as *mut _ as *mut _,
277            ptr::null_mut(),
278            0,
279        );
280    }
281    if rc < 0 {
282        cpus = 1;
283    }
284    cpus as usize
285}
286
287#[cfg(target_os = "openbsd")]
288fn get_num_physical_cpus() -> usize {
289    use std::ptr;
290
291    let mut cpus: libc::c_uint = 0;
292    let mut cpus_size = std::mem::size_of_val(&cpus);
293    let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
294    let rc: libc::c_int;
295
296    unsafe {
297        rc = libc::sysctl(
298            mib.as_mut_ptr(),
299            2,
300            &mut cpus as *mut _ as *mut _,
301            &mut cpus_size as *mut _ as *mut _,
302            ptr::null_mut(),
303            0,
304        );
305    }
306    if rc < 0 {
307        cpus = 1;
308    }
309    cpus as usize
310}
311
312#[cfg(target_os = "macos")]
313fn get_num_physical_cpus() -> usize {
314    use std::{ffi::CStr, ptr};
315
316    let mut cpus: i32 = 0;
317    let mut cpus_size = std::mem::size_of_val(&cpus);
318
319    let sysctl_name = CStr::from_bytes_with_nul(b"hw.physicalcpu\0").expect("byte literal is missing NUL");
320
321    unsafe {
322        if 0 != libc::sysctlbyname(
323            sysctl_name.as_ptr(),
324            &mut cpus as *mut _ as *mut _,
325            &mut cpus_size as *mut _ as *mut _,
326            ptr::null_mut(),
327            0,
328        ) {
329            return get_num_cpus();
330        }
331    }
332    cpus as usize
333}
334
335#[cfg(target_os = "aix")]
336fn get_num_physical_cpus() -> usize {
337    match get_smt_threads_aix() {
338        Some(num) => get_num_cpus() / num,
339        None => get_num_cpus(),
340    }
341}
342
343#[cfg(target_os = "aix")]
344fn get_smt_threads_aix() -> Option<usize> {
345    let smt = unsafe { libc::getsystemcfg(libc::SC_SMT_TC) };
346    if smt == u64::MAX {
347        return None;
348    }
349    Some(smt as usize)
350}
351
352#[cfg(any(
353    target_os = "macos",
354    target_os = "ios",
355    target_os = "android",
356    target_os = "aix",
357    target_os = "solaris",
358    target_os = "illumos",
359    target_os = "fuchsia"
360))]
361fn get_num_cpus() -> usize {
362    // On ARM targets, processors could be turned off to save power.
363    // Use `_SC_NPROCESSORS_CONF` to get the real number.
364    #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
365    const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_CONF;
366    #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
367    const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_ONLN;
368
369    let cpus = unsafe { libc::sysconf(CONF_NAME) };
370    if cpus < 1 { 1 } else { cpus as usize }
371}
372
373#[cfg(target_os = "haiku")]
374fn get_num_cpus() -> usize {
375    use std::mem;
376
377    #[allow(non_camel_case_types)]
378    type bigtime_t = i64;
379    #[allow(non_camel_case_types)]
380    type status_t = i32;
381
382    #[repr(C)]
383    pub struct system_info {
384        pub boot_time: bigtime_t,
385        pub cpu_count: u32,
386        pub max_pages: u64,
387        pub used_pages: u64,
388        pub cached_pages: u64,
389        pub block_cache_pages: u64,
390        pub ignored_pages: u64,
391        pub needed_memory: u64,
392        pub free_memory: u64,
393        pub max_swap_pages: u64,
394        pub free_swap_pages: u64,
395        pub page_faults: u32,
396        pub max_sems: u32,
397        pub used_sems: u32,
398        pub max_ports: u32,
399        pub used_ports: u32,
400        pub max_threads: u32,
401        pub used_threads: u32,
402        pub max_teams: u32,
403        pub used_teams: u32,
404        pub kernel_name: [::std::os::raw::c_char; 256usize],
405        pub kernel_build_date: [::std::os::raw::c_char; 32usize],
406        pub kernel_build_time: [::std::os::raw::c_char; 32usize],
407        pub kernel_version: i64,
408        pub abi: u32,
409    }
410
411    extern "C" {
412        fn get_system_info(info: *mut system_info) -> status_t;
413    }
414
415    let mut info: system_info = unsafe { mem::zeroed() };
416    let status = unsafe { get_system_info(&mut info as *mut _) };
417    if status == 0 { info.cpu_count as usize } else { 1 }
418}
419
420#[cfg(target_os = "hermit")]
421fn get_num_cpus() -> usize {
422    unsafe { hermit_abi::get_processor_count() }
423}
424
425#[cfg(not(any(
426    target_os = "macos",
427    target_os = "ios",
428    target_os = "android",
429    target_os = "aix",
430    target_os = "solaris",
431    target_os = "illumos",
432    target_os = "fuchsia",
433    target_os = "linux",
434    target_os = "openbsd",
435    target_os = "freebsd",
436    target_os = "dragonfly",
437    target_os = "netbsd",
438    target_os = "haiku",
439    target_os = "hermit",
440    windows,
441)))]
442fn get_num_cpus() -> usize {
443    1
444}
445
446#[cfg(test)]
447mod tests {
448    fn env_var(name: &'static str) -> Option<usize> {
449        ::std::env::var(name).ok().map(|val| val.parse().unwrap())
450    }
451
452    #[test]
453    fn test_get() {
454        let num = super::get();
455        if let Some(n) = env_var("NUM_CPUS_TEST_GET") {
456            assert_eq!(num, n);
457        } else {
458            assert!(num > 0);
459            assert!(num < 236_451);
460        }
461    }
462
463    #[test]
464    fn test_get_physical() {
465        let num = super::get_physical();
466        if let Some(n) = env_var("NUM_CPUS_TEST_GET_PHYSICAL") {
467            assert_eq!(num, n);
468        } else {
469            assert!(num > 0);
470            assert!(num < 236_451);
471        }
472    }
473}