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