Skip to content

Commit c38ddb8

Browse files
committed
Auto merge of #74480 - yoshuawuyts:hardware_threads, r=dtolnay
Add std::thread::available_concurrency This PR adds a counterpart to [C++'s `std::thread::hardware_concurrency`](https://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency) to Rust, tracking issue #74479. cc/ `@rust-lang/libs` ## Motivation Being able to know how many hardware threads a platform supports is a core part of building multi-threaded code. In C++ 11 this has become available through the [`std::thread::hardware_concurrency`](https://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency) API. Currently in Rust most of the ecosystem depends on the [`num_cpus` crate](https://docs.rs/num_cpus/1.13.0/num_cpus/) ([no.35 in top 500 crates](https://docs.google.com/spreadsheets/d/1wwahRMHG3buvnfHjmPQFU4Kyfq15oTwbfsuZpwHUKc4/edit#gid=1253069234)) to provide this functionality. This PR proposes an API to provide access to the number of hardware threads available on a given platform. __edit (2020-07-24):__ The purpose of this PR is to provide a hint for how many threads to spawn to saturate the processor. There's value in introducing APIs for NUMA and Windows processor groups, but those are intentionally out of scope for this PR. See: #74480 (comment). ## Naming Discussing the naming of the API on Zulip surfaced two options: - `std::thread::hardware_concurrency` - `std::thread::hardware_threads` Both options seemed acceptable, but overall people seem to gravitate the most towards `hardware_threads`. Additionally `@jonas-schievink` pointed out that the "hardware threads" terminology is well-established and is used in among other the [RISC-V specification](https://riscv.org/specifications/isa-spec-pdf/) (page 20): > A component is termed a core if it contains an independent instruction fetch unit. A RISC-V-compatible core might support multiple RISC-V-compatible __hardware threads__, or harts, through multithreading. It's also worth noting that [the original paper introducing C++'s `std::thread` submodule](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2320.html) unfortunately doesn't feature any discussion on the naming of `hardware_concurrency`, so we can't use that to help inform our decision here. ## Return type An important consideration `@joshtriplett` brought up is that we don't want to default to `1` for platforms where the number of available threads cannot be retrieved. Instead we want to inform the users of the fact that we don't know and allow them to handle that case. Which is why this PR uses `Option<NonZeroUsize>` as its return type, where `None` is returned on platforms where we don't know the number of hardware threads available. The reasoning for `NonZeroUsize` vs `usize` is that if the number of threads for a platform are known, they'll always be at least 1. As evidenced by the example the `NonZero*` family of APIs may currently not be the most ergonomic to use, but improving the ergonomics of them is something that I think we can address separately. ## Implementation `@Mark-Simulacrum` pointed out that most of the code we wanted to expose here was already available under `libtest`. So this PR mostly moves the internal code of libtest into a public API.
2 parents cbc42a0 + 3717646 commit c38ddb8

File tree

4 files changed

+166
-101
lines changed

4 files changed

+166
-101
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
use crate::io;
2+
use crate::num::NonZeroUsize;
3+
4+
/// Returns the number of hardware threads available to the program.
5+
///
6+
/// This value should be considered only a hint.
7+
///
8+
/// # Platform-specific behavior
9+
///
10+
/// If interpreted as the number of actual hardware threads, it may undercount on
11+
/// Windows systems with more than 64 hardware threads. If interpreted as the
12+
/// available concurrency for that process, it may overcount on Windows systems
13+
/// when limited by a process wide affinity mask or job object limitations, and
14+
/// it may overcount on Linux systems when limited by a process wide affinity
15+
/// mask or affected by cgroups limits.
16+
///
17+
/// # Errors
18+
///
19+
/// This function will return an error in the following situations, but is not
20+
/// limited to just these cases:
21+
///
22+
/// - If the number of hardware threads is not known for the target platform.
23+
/// - The process lacks permissions to view the number of hardware threads
24+
/// available.
25+
///
26+
/// # Examples
27+
///
28+
/// ```
29+
/// # #![allow(dead_code)]
30+
/// #![feature(available_concurrency)]
31+
/// use std::thread;
32+
///
33+
/// let count = thread::available_concurrency().map(|n| n.get()).unwrap_or(1);
34+
/// ```
35+
#[unstable(feature = "available_concurrency", issue = "74479")]
36+
pub fn available_concurrency() -> io::Result<NonZeroUsize> {
37+
available_concurrency_internal()
38+
}
39+
40+
cfg_if::cfg_if! {
41+
if #[cfg(windows)] {
42+
#[allow(nonstandard_style)]
43+
fn available_concurrency_internal() -> io::Result<NonZeroUsize> {
44+
#[repr(C)]
45+
struct SYSTEM_INFO {
46+
wProcessorArchitecture: u16,
47+
wReserved: u16,
48+
dwPageSize: u32,
49+
lpMinimumApplicationAddress: *mut u8,
50+
lpMaximumApplicationAddress: *mut u8,
51+
dwActiveProcessorMask: *mut u8,
52+
dwNumberOfProcessors: u32,
53+
dwProcessorType: u32,
54+
dwAllocationGranularity: u32,
55+
wProcessorLevel: u16,
56+
wProcessorRevision: u16,
57+
}
58+
extern "system" {
59+
fn GetSystemInfo(info: *mut SYSTEM_INFO) -> i32;
60+
}
61+
let res = unsafe {
62+
let mut sysinfo = crate::mem::zeroed();
63+
GetSystemInfo(&mut sysinfo);
64+
sysinfo.dwNumberOfProcessors as usize
65+
};
66+
match res {
67+
0 => Err(io::Error::new(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform")),
68+
cpus => Ok(unsafe { NonZeroUsize::new_unchecked(cpus) }),
69+
}
70+
}
71+
} else if #[cfg(any(
72+
target_os = "android",
73+
target_os = "cloudabi",
74+
target_os = "emscripten",
75+
target_os = "fuchsia",
76+
target_os = "ios",
77+
target_os = "linux",
78+
target_os = "macos",
79+
target_os = "solaris",
80+
target_os = "illumos",
81+
))] {
82+
fn available_concurrency_internal() -> io::Result<NonZeroUsize> {
83+
match unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) } {
84+
-1 => Err(io::Error::last_os_error()),
85+
0 => Err(io::Error::new(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform")),
86+
cpus => Ok(unsafe { NonZeroUsize::new_unchecked(cpus as usize) }),
87+
}
88+
}
89+
} else if #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd"))] {
90+
fn available_concurrency_internal() -> io::Result<NonZeroUsize> {
91+
use crate::ptr;
92+
93+
let mut cpus: libc::c_uint = 0;
94+
let mut cpus_size = crate::mem::size_of_val(&cpus);
95+
96+
unsafe {
97+
cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint;
98+
}
99+
100+
// Fallback approach in case of errors or no hardware threads.
101+
if cpus < 1 {
102+
let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
103+
let res = unsafe {
104+
libc::sysctl(
105+
mib.as_mut_ptr(),
106+
2,
107+
&mut cpus as *mut _ as *mut _,
108+
&mut cpus_size as *mut _ as *mut _,
109+
ptr::null_mut(),
110+
0,
111+
)
112+
};
113+
114+
// Handle errors if any.
115+
if res == -1 {
116+
return Err(io::Error::last_os_error());
117+
} else if cpus == 0 {
118+
return Err(io::Error::new(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"));
119+
}
120+
}
121+
Ok(unsafe { NonZeroUsize::new_unchecked(cpus as usize) })
122+
}
123+
} else if #[cfg(target_os = "openbsd")] {
124+
fn available_concurrency_internal() -> io::Result<NonZeroUsize> {
125+
use crate::ptr;
126+
127+
let mut cpus: libc::c_uint = 0;
128+
let mut cpus_size = crate::mem::size_of_val(&cpus);
129+
let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
130+
131+
let res = unsafe {
132+
libc::sysctl(
133+
mib.as_mut_ptr(),
134+
2,
135+
&mut cpus as *mut _ as *mut _,
136+
&mut cpus_size as *mut _ as *mut _,
137+
ptr::null_mut(),
138+
0,
139+
)
140+
};
141+
142+
// Handle errors if any.
143+
if res == -1 {
144+
return Err(io::Error::last_os_error());
145+
} else if cpus == 0 {
146+
return Err(io::Error::new(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"));
147+
}
148+
149+
Ok(unsafe { NonZeroUsize::new_unchecked(cpus as usize) })
150+
}
151+
} else {
152+
// FIXME: implement on vxWorks, Redox, HermitCore, Haiku, l4re
153+
fn available_concurrency_internal() -> io::Result<NonZeroUsize> {
154+
Err(io::Error::new(io::ErrorKind::NotFound, "The number of hardware threads is not known for the target platform"))
155+
}
156+
}
157+
}

library/std/src/thread/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,15 @@ use crate::time::Duration;
175175
#[macro_use]
176176
mod local;
177177

178+
#[unstable(feature = "available_concurrency", issue = "74479")]
179+
mod available_concurrency;
180+
178181
#[stable(feature = "rust1", since = "1.0.0")]
179182
pub use self::local::{AccessError, LocalKey};
180183

184+
#[unstable(feature = "available_concurrency", issue = "74479")]
185+
pub use available_concurrency::available_concurrency;
186+
181187
// The types used by the thread_local! macro to access TLS keys. Note that there
182188
// are two types, the "OS" type and the "fast" type. The OS thread local key
183189
// type is accessed via platform-specific API calls and is slow, while the fast
+2-101
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Helper module which helps to determine amount of threads to be used
22
//! during tests execution.
33
use std::env;
4+
use std::thread;
45

56
#[allow(deprecated)]
67
pub fn get_concurrency() -> usize {
@@ -12,106 +13,6 @@ pub fn get_concurrency() -> usize {
1213
_ => panic!("RUST_TEST_THREADS is `{}`, should be a positive integer.", s),
1314
}
1415
}
15-
Err(..) => num_cpus(),
16-
}
17-
}
18-
19-
cfg_if::cfg_if! {
20-
if #[cfg(windows)] {
21-
#[allow(nonstandard_style)]
22-
fn num_cpus() -> usize {
23-
#[repr(C)]
24-
struct SYSTEM_INFO {
25-
wProcessorArchitecture: u16,
26-
wReserved: u16,
27-
dwPageSize: u32,
28-
lpMinimumApplicationAddress: *mut u8,
29-
lpMaximumApplicationAddress: *mut u8,
30-
dwActiveProcessorMask: *mut u8,
31-
dwNumberOfProcessors: u32,
32-
dwProcessorType: u32,
33-
dwAllocationGranularity: u32,
34-
wProcessorLevel: u16,
35-
wProcessorRevision: u16,
36-
}
37-
extern "system" {
38-
fn GetSystemInfo(info: *mut SYSTEM_INFO) -> i32;
39-
}
40-
unsafe {
41-
let mut sysinfo = std::mem::zeroed();
42-
GetSystemInfo(&mut sysinfo);
43-
sysinfo.dwNumberOfProcessors as usize
44-
}
45-
}
46-
} else if #[cfg(any(
47-
target_os = "android",
48-
target_os = "cloudabi",
49-
target_os = "emscripten",
50-
target_os = "fuchsia",
51-
target_os = "ios",
52-
target_os = "linux",
53-
target_os = "macos",
54-
target_os = "solaris",
55-
target_os = "illumos",
56-
))] {
57-
fn num_cpus() -> usize {
58-
unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as usize }
59-
}
60-
} else if #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd"))] {
61-
fn num_cpus() -> usize {
62-
use std::ptr;
63-
64-
let mut cpus: libc::c_uint = 0;
65-
let mut cpus_size = std::mem::size_of_val(&cpus);
66-
67-
unsafe {
68-
cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint;
69-
}
70-
if cpus < 1 {
71-
let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
72-
unsafe {
73-
libc::sysctl(
74-
mib.as_mut_ptr(),
75-
2,
76-
&mut cpus as *mut _ as *mut _,
77-
&mut cpus_size as *mut _ as *mut _,
78-
ptr::null_mut(),
79-
0,
80-
);
81-
}
82-
if cpus < 1 {
83-
cpus = 1;
84-
}
85-
}
86-
cpus as usize
87-
}
88-
} else if #[cfg(target_os = "openbsd")] {
89-
fn num_cpus() -> usize {
90-
use std::ptr;
91-
92-
let mut cpus: libc::c_uint = 0;
93-
let mut cpus_size = std::mem::size_of_val(&cpus);
94-
let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0];
95-
96-
unsafe {
97-
libc::sysctl(
98-
mib.as_mut_ptr(),
99-
2,
100-
&mut cpus as *mut _ as *mut _,
101-
&mut cpus_size as *mut _ as *mut _,
102-
ptr::null_mut(),
103-
0,
104-
);
105-
}
106-
if cpus < 1 {
107-
cpus = 1;
108-
}
109-
cpus as usize
110-
}
111-
} else {
112-
// FIXME: implement on vxWorks, Redox, HermitCore, Haiku, l4re
113-
fn num_cpus() -> usize {
114-
1
115-
}
16+
Err(..) => thread::available_concurrency().map(|n| n.get()).unwrap_or(1),
11617
}
11718
}

library/test/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#![feature(rustc_private)]
2525
#![feature(nll)]
2626
#![feature(bool_to_option)]
27+
#![feature(available_concurrency)]
2728
#![feature(set_stdio)]
2829
#![feature(panic_unwind)]
2930
#![feature(staged_api)]

0 commit comments

Comments
 (0)