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))]
34 extern crate libc;
35 
36 #[cfg(target_os = "hermit")]
37 extern crate hermit_abi;
38 
39 #[cfg(target_os = "linux")]
40 mod linux;
41 #[cfg(target_os = "linux")]
42 use 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]
get() -> usize73 pub 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]
get_physical() -> usize108 pub 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]
get_num_physical_cpus() -> usize120 fn get_num_physical_cpus() -> usize {
121     // Not implemented, fall back
122     get_num_cpus()
123 }
124 
125 #[cfg(target_os = "windows")]
get_num_physical_cpus() -> usize126 fn 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")]
get_num_physical_cpus_windows() -> Option<usize>134 fn 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)]
get_num_cpus() -> usize209 fn 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"))]
get_num_cpus() -> usize239 fn 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")]
get_num_cpus() -> usize266 fn 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")]
get_num_physical_cpus() -> usize289 fn 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")]
get_num_physical_cpus() -> usize313 fn 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")]
get_num_physical_cpus() -> usize336 fn 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")]
get_smt_threads_aix() -> Option<usize>344 fn 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 )]
get_num_cpus() -> usize364 fn 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")]
get_num_cpus() -> usize381 fn 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")]
get_num_cpus() -> usize432 fn 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 )))]
get_num_cpus() -> usize454 fn get_num_cpus() -> usize {
455     1
456 }
457 
458 #[cfg(test)]
459 mod tests {
env_var(name: &'static str) -> Option<usize>460     fn env_var(name: &'static str) -> Option<usize> {
461         ::std::env::var(name).ok().map(|val| val.parse().unwrap())
462     }
463 
464     #[test]
test_get()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]
test_get_physical()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