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}
486