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
113#[cfg(not(any(
114    target_os = "linux",
115    target_os = "windows",
116    target_os = "macos",
117    target_os = "openbsd",
118    target_os = "aix")))]
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::ptr;
138    use std::mem;
139
140    #[allow(non_upper_case_globals)]
141    const RelationProcessorCore: u32 = 0;
142
143    #[repr(C)]
144    #[allow(non_camel_case_types)]
145    struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION {
146        mask: usize,
147        relationship: u32,
148        _unused: [u64; 2]
149    }
150
151    extern "system" {
152        fn GetLogicalProcessorInformation(
153            info: *mut SYSTEM_LOGICAL_PROCESSOR_INFORMATION,
154            length: &mut u32
155        ) -> u32;
156    }
157
158    // First we need to determine how much space to reserve.
159
160    // The required size of the buffer, in bytes.
161    let mut needed_size = 0;
162
163    unsafe {
164        GetLogicalProcessorInformation(ptr::null_mut(), &mut needed_size);
165    }
166
167    let struct_size = mem::size_of::<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>() as u32;
168
169    // Could be 0, or some other bogus size.
170    if needed_size == 0 || needed_size < struct_size || needed_size % struct_size != 0 {
171        return None;
172    }
173
174    let count = needed_size / struct_size;
175
176    // Allocate some memory where we will store the processor info.
177    let mut buf = Vec::with_capacity(count as usize);
178
179    let result;
180
181    unsafe {
182        result = GetLogicalProcessorInformation(buf.as_mut_ptr(), &mut needed_size);
183    }
184
185    // Failed for any reason.
186    if result == 0 {
187        return None;
188    }
189
190    let count = needed_size / struct_size;
191
192    unsafe {
193        buf.set_len(count as usize);
194    }
195
196    let phys_proc_count = buf.iter()
197        // Only interested in processor packages (physical processors.)
198        .filter(|proc_info| proc_info.relationship == RelationProcessorCore)
199        .count();
200
201    if phys_proc_count == 0 {
202        None
203    } else {
204        Some(phys_proc_count)
205    }
206}
207
208#[cfg(windows)]
209fn get_num_cpus() -> usize {
210    #[repr(C)]
211    struct SYSTEM_INFO {
212        wProcessorArchitecture: u16,
213        wReserved: u16,
214        dwPageSize: u32,
215        lpMinimumApplicationAddress: *mut u8,
216        lpMaximumApplicationAddress: *mut u8,
217        dwActiveProcessorMask: *mut u8,
218        dwNumberOfProcessors: u32,
219        dwProcessorType: u32,
220        dwAllocationGranularity: u32,
221        wProcessorLevel: u16,
222        wProcessorRevision: u16,
223    }
224
225    extern "system" {
226        fn GetSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
227    }
228
229    unsafe {
230        let mut sysinfo: SYSTEM_INFO = std::mem::zeroed();
231        GetSystemInfo(&mut sysinfo);
232        sysinfo.dwNumberOfProcessors as usize
233    }
234}
235
236#[cfg(any(target_os = "freebsd",
237          target_os = "dragonfly",
238          target_os = "netbsd"))]
239fn get_num_cpus() -> usize {
240    use std::ptr;
241
242    let mut cpus: libc::c_uint = 0;
243    let mut cpus_size = std::mem::size_of_val(&cpus);
244
245    unsafe {
246        cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint;
247    }
248    if cpus < 1 {
249        let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
250        unsafe {
251            libc::sysctl(mib.as_mut_ptr(),
252                         2,
253                         &mut cpus as *mut _ as *mut _,
254                         &mut cpus_size as *mut _ as *mut _,
255                         ptr::null_mut(),
256                         0);
257        }
258        if cpus < 1 {
259            cpus = 1;
260        }
261    }
262    cpus as usize
263}
264
265#[cfg(target_os = "openbsd")]
266fn get_num_cpus() -> usize {
267    use std::ptr;
268
269    let mut cpus: libc::c_uint = 0;
270    let mut cpus_size = std::mem::size_of_val(&cpus);
271    let mut mib = [libc::CTL_HW, libc::HW_NCPUONLINE, 0, 0];
272    let rc: libc::c_int;
273
274    unsafe {
275        rc = libc::sysctl(mib.as_mut_ptr(),
276                          2,
277                          &mut cpus as *mut _ as *mut _,
278                          &mut cpus_size as *mut _ as *mut _,
279                          ptr::null_mut(),
280                          0);
281    }
282    if rc < 0 {
283        cpus = 1;
284    }
285    cpus as usize
286}
287
288#[cfg(target_os = "openbsd")]
289fn get_num_physical_cpus() -> usize {
290    use std::ptr;
291
292    let mut cpus: libc::c_uint = 0;
293    let mut cpus_size = std::mem::size_of_val(&cpus);
294    let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
295    let rc: libc::c_int;
296
297    unsafe {
298        rc = libc::sysctl(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    if rc < 0 {
306        cpus = 1;
307    }
308    cpus as usize
309}
310
311
312#[cfg(target_os = "macos")]
313fn get_num_physical_cpus() -> usize {
314    use std::ffi::CStr;
315    use std::ptr;
316
317    let mut cpus: i32 = 0;
318    let mut cpus_size = std::mem::size_of_val(&cpus);
319
320    let sysctl_name = CStr::from_bytes_with_nul(b"hw.physicalcpu\0")
321        .expect("byte literal is missing NUL");
322
323    unsafe {
324        if 0 != libc::sysctlbyname(sysctl_name.as_ptr(),
325                                   &mut cpus as *mut _ as *mut _,
326                                   &mut cpus_size as *mut _ as *mut _,
327                                   ptr::null_mut(),
328                                   0) {
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 {
346        libc::getsystemcfg(libc::SC_SMT_TC)
347    };
348    if smt == u64::MAX {
349        return None;
350    }
351    Some(smt as usize)
352}
353
354#[cfg(any(
355    target_os = "nacl",
356    target_os = "macos",
357    target_os = "ios",
358    target_os = "android",
359    target_os = "aix",
360    target_os = "solaris",
361    target_os = "illumos",
362    target_os = "fuchsia")
363)]
364fn get_num_cpus() -> usize {
365    // On ARM targets, processors could be turned off to save power.
366    // Use `_SC_NPROCESSORS_CONF` to get the real number.
367    #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
368    const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_CONF;
369    #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
370    const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_ONLN;
371
372    let cpus = unsafe { libc::sysconf(CONF_NAME) };
373    if cpus < 1 {
374        1
375    } else {
376        cpus as usize
377    }
378}
379
380#[cfg(target_os = "haiku")]
381fn get_num_cpus() -> usize {
382    use std::mem;
383
384    #[allow(non_camel_case_types)]
385    type bigtime_t = i64;
386    #[allow(non_camel_case_types)]
387    type status_t = i32;
388
389    #[repr(C)]
390    pub struct system_info {
391        pub boot_time: bigtime_t,
392        pub cpu_count: u32,
393        pub max_pages: u64,
394        pub used_pages: u64,
395        pub cached_pages: u64,
396        pub block_cache_pages: u64,
397        pub ignored_pages: u64,
398        pub needed_memory: u64,
399        pub free_memory: u64,
400        pub max_swap_pages: u64,
401        pub free_swap_pages: u64,
402        pub page_faults: u32,
403        pub max_sems: u32,
404        pub used_sems: u32,
405        pub max_ports: u32,
406        pub used_ports: u32,
407        pub max_threads: u32,
408        pub used_threads: u32,
409        pub max_teams: u32,
410        pub used_teams: u32,
411        pub kernel_name: [::std::os::raw::c_char; 256usize],
412        pub kernel_build_date: [::std::os::raw::c_char; 32usize],
413        pub kernel_build_time: [::std::os::raw::c_char; 32usize],
414        pub kernel_version: i64,
415        pub abi: u32,
416    }
417
418    extern {
419        fn get_system_info(info: *mut system_info) -> status_t;
420    }
421
422    let mut info: system_info = unsafe { mem::zeroed() };
423    let status = unsafe { get_system_info(&mut info as *mut _) };
424    if status == 0 {
425        info.cpu_count as usize
426    } else {
427        1
428    }
429}
430
431#[cfg(target_os = "hermit")]
432fn get_num_cpus() -> usize {
433    unsafe { hermit_abi::get_processor_count() }
434}
435
436#[cfg(not(any(
437    target_os = "nacl",
438    target_os = "macos",
439    target_os = "ios",
440    target_os = "android",
441    target_os = "aix",
442    target_os = "solaris",
443    target_os = "illumos",
444    target_os = "fuchsia",
445    target_os = "linux",
446    target_os = "openbsd",
447    target_os = "freebsd",
448    target_os = "dragonfly",
449    target_os = "netbsd",
450    target_os = "haiku",
451    target_os = "hermit",
452    windows,
453)))]
454fn get_num_cpus() -> usize {
455    1
456}
457
458#[cfg(test)]
459mod tests {
460    fn env_var(name: &'static str) -> Option<usize> {
461        ::std::env::var(name).ok().map(|val| val.parse().unwrap())
462    }
463
464    #[test]
465    fn test_get() {
466        let num = super::get();
467        if let Some(n) = env_var("NUM_CPUS_TEST_GET") {
468            assert_eq!(num, n);
469        } else {
470            assert!(num > 0);
471            assert!(num < 236_451);
472        }
473    }
474
475    #[test]
476    fn test_get_physical() {
477        let num = super::get_physical();
478        if let Some(n) = env_var("NUM_CPUS_TEST_GET_PHYSICAL") {
479            assert_eq!(num, n);
480        } else {
481            assert!(num > 0);
482            assert!(num < 236_451);
483        }
484    }
485}