//! A crate with utilities to determine the number of CPUs available on the //! current system. //! //! Sometimes the CPU will exaggerate the number of CPUs it contains, because it can use //! [processor tricks] to deliver increased performance when there are more threads. This //! crate provides methods to get both the logical and physical numbers of cores. //! //! This information can be used as a guide to how many tasks can be run in parallel. //! There are many properties of the system architecture that will affect parallelism, //! for example memory access speeds (for all the caches and RAM) and the physical //! architecture of the processor, so the number of CPUs should be used as a rough guide //! only. //! //! //! ## Examples //! //! Fetch the number of logical CPUs. //! //! ``` //! let cpus = num_cpus::get(); //! ``` //! //! See [`rayon::Threadpool`] for an example of where the number of CPUs could be //! used when setting up parallel jobs (Where the threadpool example uses a fixed //! number 8, it could use the number of CPUs). //! //! [processor tricks]: https://en.wikipedia.org/wiki/Simultaneous_multithreading //! [`rayon::ThreadPool`]: https://docs.rs/rayon/1.*/rayon/struct.ThreadPool.html #![cfg_attr(test, deny(warnings))] #![deny(missing_docs)] #![allow(non_snake_case)] #[cfg(not(windows))] extern crate libc; #[cfg(target_os = "hermit")] extern crate hermit_abi; #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "linux")] use linux::{get_num_cpus, get_num_physical_cpus}; /// Returns the number of available CPUs of the current system. /// /// This function will get the number of logical cores. Sometimes this is different from the number /// of physical cores (See [Simultaneous multithreading on Wikipedia][smt]). /// /// This will always return at least `1`. /// /// # Examples /// /// ``` /// let cpus = num_cpus::get(); /// if cpus > 1 { /// println!("We are on a multicore system with {} CPUs", cpus); /// } else { /// println!("We are on a single core system"); /// } /// ``` /// /// # Note /// /// This will check [sched affinity] on Linux, showing a lower number of CPUs if the current /// thread does not have access to all the computer's CPUs. /// /// This will also check [cgroups], frequently used in containers to constrain CPU usage. /// /// [smt]: https://en.wikipedia.org/wiki/Simultaneous_multithreading /// [sched affinity]: http://www.gnu.org/software/libc/manual/html_node/CPU-Affinity.html /// [cgroups]: https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt #[inline] pub fn get() -> usize { get_num_cpus() } /// Returns the number of physical cores of the current system. /// /// This will always return at least `1`. /// /// # Note /// /// Physical count is supported only on Linux, mac OS and Windows platforms. /// On other platforms, or if the physical count fails on supported platforms, /// this function returns the same as [`get()`], which is the number of logical /// CPUS. /// /// # Examples /// /// ``` /// let logical_cpus = num_cpus::get(); /// let physical_cpus = num_cpus::get_physical(); /// if logical_cpus > physical_cpus { /// println!("We have simultaneous multithreading with about {:.2} \ /// logical cores to 1 physical core.", /// (logical_cpus as f64) / (physical_cpus as f64)); /// } else if logical_cpus == physical_cpus { /// println!("Either we don't have simultaneous multithreading, or our \ /// system doesn't support getting the number of physical CPUs."); /// } else { /// println!("We have less logical CPUs than physical CPUs, maybe we only have access to \ /// some of the CPUs on our system."); /// } /// ``` /// /// [`get()`]: fn.get.html #[inline] pub fn get_physical() -> usize { get_num_physical_cpus() } #[cfg(not(any( target_os = "linux", target_os = "windows", target_os = "macos", target_os = "openbsd", target_os = "aix")))] #[inline] fn get_num_physical_cpus() -> usize { // Not implemented, fall back get_num_cpus() } #[cfg(target_os = "windows")] fn get_num_physical_cpus() -> usize { match get_num_physical_cpus_windows() { Some(num) => num, None => get_num_cpus() } } #[cfg(target_os = "windows")] fn get_num_physical_cpus_windows() -> Option { // Inspired by https://msdn.microsoft.com/en-us/library/ms683194 use std::ptr; use std::mem; #[allow(non_upper_case_globals)] const RelationProcessorCore: u32 = 0; #[repr(C)] #[allow(non_camel_case_types)] struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION { mask: usize, relationship: u32, _unused: [u64; 2] } extern "system" { fn GetLogicalProcessorInformation( info: *mut SYSTEM_LOGICAL_PROCESSOR_INFORMATION, length: &mut u32 ) -> u32; } // First we need to determine how much space to reserve. // The required size of the buffer, in bytes. let mut needed_size = 0; unsafe { GetLogicalProcessorInformation(ptr::null_mut(), &mut needed_size); } let struct_size = mem::size_of::() as u32; // Could be 0, or some other bogus size. if needed_size == 0 || needed_size < struct_size || needed_size % struct_size != 0 { return None; } let count = needed_size / struct_size; // Allocate some memory where we will store the processor info. let mut buf = Vec::with_capacity(count as usize); let result; unsafe { result = GetLogicalProcessorInformation(buf.as_mut_ptr(), &mut needed_size); } // Failed for any reason. if result == 0 { return None; } let count = needed_size / struct_size; unsafe { buf.set_len(count as usize); } let phys_proc_count = buf.iter() // Only interested in processor packages (physical processors.) .filter(|proc_info| proc_info.relationship == RelationProcessorCore) .count(); if phys_proc_count == 0 { None } else { Some(phys_proc_count) } } #[cfg(windows)] fn get_num_cpus() -> usize { #[repr(C)] struct SYSTEM_INFO { wProcessorArchitecture: u16, wReserved: u16, dwPageSize: u32, lpMinimumApplicationAddress: *mut u8, lpMaximumApplicationAddress: *mut u8, dwActiveProcessorMask: *mut u8, dwNumberOfProcessors: u32, dwProcessorType: u32, dwAllocationGranularity: u32, wProcessorLevel: u16, wProcessorRevision: u16, } extern "system" { fn GetSystemInfo(lpSystemInfo: *mut SYSTEM_INFO); } unsafe { let mut sysinfo: SYSTEM_INFO = std::mem::zeroed(); GetSystemInfo(&mut sysinfo); sysinfo.dwNumberOfProcessors as usize } } #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd"))] fn get_num_cpus() -> usize { use std::ptr; let mut cpus: libc::c_uint = 0; let mut cpus_size = std::mem::size_of_val(&cpus); unsafe { cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint; } if cpus < 1 { let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; unsafe { libc::sysctl(mib.as_mut_ptr(), 2, &mut cpus as *mut _ as *mut _, &mut cpus_size as *mut _ as *mut _, ptr::null_mut(), 0); } if cpus < 1 { cpus = 1; } } cpus as usize } #[cfg(target_os = "openbsd")] fn get_num_cpus() -> usize { use std::ptr; let mut cpus: libc::c_uint = 0; let mut cpus_size = std::mem::size_of_val(&cpus); let mut mib = [libc::CTL_HW, libc::HW_NCPUONLINE, 0, 0]; let rc: libc::c_int; unsafe { rc = libc::sysctl(mib.as_mut_ptr(), 2, &mut cpus as *mut _ as *mut _, &mut cpus_size as *mut _ as *mut _, ptr::null_mut(), 0); } if rc < 0 { cpus = 1; } cpus as usize } #[cfg(target_os = "openbsd")] fn get_num_physical_cpus() -> usize { use std::ptr; let mut cpus: libc::c_uint = 0; let mut cpus_size = std::mem::size_of_val(&cpus); let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; let rc: libc::c_int; unsafe { rc = libc::sysctl(mib.as_mut_ptr(), 2, &mut cpus as *mut _ as *mut _, &mut cpus_size as *mut _ as *mut _, ptr::null_mut(), 0); } if rc < 0 { cpus = 1; } cpus as usize } #[cfg(target_os = "macos")] fn get_num_physical_cpus() -> usize { use std::ffi::CStr; use std::ptr; let mut cpus: i32 = 0; let mut cpus_size = std::mem::size_of_val(&cpus); let sysctl_name = CStr::from_bytes_with_nul(b"hw.physicalcpu\0") .expect("byte literal is missing NUL"); unsafe { if 0 != libc::sysctlbyname(sysctl_name.as_ptr(), &mut cpus as *mut _ as *mut _, &mut cpus_size as *mut _ as *mut _, ptr::null_mut(), 0) { return get_num_cpus(); } } cpus as usize } #[cfg(target_os = "aix")] fn get_num_physical_cpus() -> usize { match get_smt_threads_aix() { Some(num) => get_num_cpus() / num, None => get_num_cpus(), } } #[cfg(target_os = "aix")] fn get_smt_threads_aix() -> Option { let smt = unsafe { libc::getsystemcfg(libc::SC_SMT_TC) }; if smt == u64::MAX { return None; } Some(smt as usize) } #[cfg(any( target_os = "nacl", target_os = "macos", target_os = "ios", target_os = "android", target_os = "aix", target_os = "solaris", target_os = "illumos", target_os = "fuchsia") )] fn get_num_cpus() -> usize { // On ARM targets, processors could be turned off to save power. // Use `_SC_NPROCESSORS_CONF` to get the real number. #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_CONF; #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_ONLN; let cpus = unsafe { libc::sysconf(CONF_NAME) }; if cpus < 1 { 1 } else { cpus as usize } } #[cfg(target_os = "haiku")] fn get_num_cpus() -> usize { use std::mem; #[allow(non_camel_case_types)] type bigtime_t = i64; #[allow(non_camel_case_types)] type status_t = i32; #[repr(C)] pub struct system_info { pub boot_time: bigtime_t, pub cpu_count: u32, pub max_pages: u64, pub used_pages: u64, pub cached_pages: u64, pub block_cache_pages: u64, pub ignored_pages: u64, pub needed_memory: u64, pub free_memory: u64, pub max_swap_pages: u64, pub free_swap_pages: u64, pub page_faults: u32, pub max_sems: u32, pub used_sems: u32, pub max_ports: u32, pub used_ports: u32, pub max_threads: u32, pub used_threads: u32, pub max_teams: u32, pub used_teams: u32, pub kernel_name: [::std::os::raw::c_char; 256usize], pub kernel_build_date: [::std::os::raw::c_char; 32usize], pub kernel_build_time: [::std::os::raw::c_char; 32usize], pub kernel_version: i64, pub abi: u32, } extern { fn get_system_info(info: *mut system_info) -> status_t; } let mut info: system_info = unsafe { mem::zeroed() }; let status = unsafe { get_system_info(&mut info as *mut _) }; if status == 0 { info.cpu_count as usize } else { 1 } } #[cfg(target_os = "hermit")] fn get_num_cpus() -> usize { unsafe { hermit_abi::get_processor_count() } } #[cfg(not(any( target_os = "nacl", target_os = "macos", target_os = "ios", target_os = "android", target_os = "aix", target_os = "solaris", target_os = "illumos", target_os = "fuchsia", target_os = "linux", target_os = "openbsd", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "haiku", target_os = "hermit", windows, )))] fn get_num_cpus() -> usize { 1 } #[cfg(test)] mod tests { fn env_var(name: &'static str) -> Option { ::std::env::var(name).ok().map(|val| val.parse().unwrap()) } #[test] fn test_get() { let num = super::get(); if let Some(n) = env_var("NUM_CPUS_TEST_GET") { assert_eq!(num, n); } else { assert!(num > 0); assert!(num < 236_451); } } #[test] fn test_get_physical() { let num = super::get_physical(); if let Some(n) = env_var("NUM_CPUS_TEST_GET_PHYSICAL") { assert_eq!(num, n); } else { assert!(num > 0); assert!(num < 236_451); } } }