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