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