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