Skip to content

Commit 03a069c

Browse files
Rollup merge of rust-lang#106144 - tgross35:patch-1, r=Mark-Simulacrum
Improve the documentation of `black_box` There don't seem to be many great resources on how `black_box` should be used, so I added some information here
2 parents 6472f31 + 13e25b8 commit 03a069c

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

library/core/src/hint.rs

+69
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,75 @@ pub fn spin_loop() {
219219
/// backend used. Programs cannot rely on `black_box` for *correctness* in any way.
220220
///
221221
/// [`std::convert::identity`]: crate::convert::identity
222+
///
223+
/// # When is this useful?
224+
///
225+
/// First and foremost: `black_box` does _not_ guarantee any exact behavior and, in some cases, may
226+
/// do nothing at all. As such, it **must not be relied upon to control critical program behavior.**
227+
/// This _immediately_ precludes any direct use of this function for cryptographic or security
228+
/// purposes.
229+
///
230+
/// While not suitable in those mission-critical cases, `back_box`'s functionality can generally be
231+
/// relied upon for benchmarking, and should be used there. It will try to ensure that the
232+
/// compiler doesn't optimize away part of the intended test code based on context. For
233+
/// example:
234+
///
235+
/// ```
236+
/// fn contains(haystack: &[&str], needle: &str) -> bool {
237+
/// haystack.iter().any(|x| x == &needle)
238+
/// }
239+
///
240+
/// pub fn benchmark() {
241+
/// let haystack = vec!["abc", "def", "ghi", "jkl", "mno"];
242+
/// let needle = "ghi";
243+
/// for _ in 0..10 {
244+
/// contains(&haystack, needle);
245+
/// }
246+
/// }
247+
/// ```
248+
///
249+
/// The compiler could theoretically make optimizations like the following:
250+
///
251+
/// - `needle` and `haystack` are always the same, move the call to `contains` outside the loop and
252+
/// delete the loop
253+
/// - Inline `contains`
254+
/// - `needle` and `haystack` have values known at compile time, `contains` is always true. Remove
255+
/// the call and replace with `true`
256+
/// - Nothing is done with the result of `contains`: delete this function call entirely
257+
/// - `benchmark` now has no purpose: delete this function
258+
///
259+
/// It is not likely that all of the above happens, but the compiler is definitely able to make some
260+
/// optimizations that could result in a very inaccurate benchmark. This is where `black_box` comes
261+
/// in:
262+
///
263+
/// ```
264+
/// use std::hint::black_box;
265+
///
266+
/// // Same `contains` function
267+
/// fn contains(haystack: &[&str], needle: &str) -> bool {
268+
/// haystack.iter().any(|x| x == &needle)
269+
/// }
270+
///
271+
/// pub fn benchmark() {
272+
/// let haystack = vec!["abc", "def", "ghi", "jkl", "mno"];
273+
/// let needle = "ghi";
274+
/// for _ in 0..10 {
275+
/// // Adjust our benchmark loop contents
276+
/// black_box(contains(black_box(&haystack), black_box(needle)));
277+
/// }
278+
/// }
279+
/// ```
280+
///
281+
/// This essentially tells the compiler to block optimizations across any calls to `black_box`. So,
282+
/// it now:
283+
///
284+
/// - Treats both arguments to `contains` as unpredictable: the body of `contains` can no longer be
285+
/// optimized based on argument values
286+
/// - Treats the call to `contains` and its result as volatile: the body of `benchmark` cannot
287+
/// optimize this away
288+
///
289+
/// This makes our benchmark much more realistic to how the function would be used in situ, where
290+
/// arguments are usually not known at compile time and the result is used in some way.
222291
#[inline]
223292
#[stable(feature = "bench_black_box", since = "1.66.0")]
224293
#[rustc_const_unstable(feature = "const_black_box", issue = "none")]

0 commit comments

Comments
 (0)