Skip to content

Commit 01ca829

Browse files
Rollup merge of #77264 - fusion-engineering-forks:skip-local-stdio, r=dtolnay
Only use LOCAL_{STDOUT,STDERR} when set_{print/panic} is used. The thread local `LOCAL_STDOUT` and `LOCAL_STDERR` are only used by the `test` crate to capture output from tests when running them in the same process in differen threads. However, every program will check these variables on every print, even outside of testing. This involves allocating a thread local key, and registering a thread local destructor. This can be somewhat expensive. This change keeps a global flag (`LOCAL_STREAMS`) which will be set to `true` when either of these local streams is used. (So, effectively only in test and benchmark runs.) When this flag is off, these thread locals are not even looked at and therefore will not be initialized on the first output on every thread, which also means no thread local destructors will be registered. --- Together with #77154, this should make output a little bit more efficient.
2 parents 6522868 + de597fc commit 01ca829

File tree

2 files changed

+61
-23
lines changed

2 files changed

+61
-23
lines changed

library/std/src/io/stdio.rs

+60-23
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,41 @@ use crate::cell::RefCell;
99
use crate::fmt;
1010
use crate::io::{self, BufReader, Initializer, IoSlice, IoSliceMut, LineWriter};
1111
use crate::lazy::SyncOnceCell;
12+
use crate::sync::atomic::{AtomicBool, Ordering};
1213
use crate::sync::{Mutex, MutexGuard};
1314
use crate::sys::stdio;
1415
use crate::sys_common;
1516
use crate::sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
1617
use crate::thread::LocalKey;
1718

1819
thread_local! {
19-
/// Stdout used by print! and println! macros
20+
/// Used by the test crate to capture the output of the print! and println! macros.
2021
static LOCAL_STDOUT: RefCell<Option<Box<dyn Write + Send>>> = {
2122
RefCell::new(None)
2223
}
2324
}
2425

2526
thread_local! {
26-
/// Stderr used by eprint! and eprintln! macros, and panics
27+
/// Used by the test crate to capture the output of the eprint! and eprintln! macros, and panics.
2728
static LOCAL_STDERR: RefCell<Option<Box<dyn Write + Send>>> = {
2829
RefCell::new(None)
2930
}
3031
}
3132

33+
/// Flag to indicate LOCAL_STDOUT and/or LOCAL_STDERR is used.
34+
///
35+
/// If both are None and were never set on any thread, this flag is set to
36+
/// false, and both LOCAL_STDOUT and LOCAL_STDOUT can be safely ignored on all
37+
/// threads, saving some time and memory registering an unused thread local.
38+
///
39+
/// Note about memory ordering: This contains information about whether two
40+
/// thread local variables might be in use. Although this is a global flag, the
41+
/// memory ordering between threads does not matter: we only want this flag to
42+
/// have a consistent order between set_print/set_panic and print_to *within
43+
/// the same thread*. Within the same thread, things always have a perfectly
44+
/// consistent order. So Ordering::Relaxed is fine.
45+
static LOCAL_STREAMS: AtomicBool = AtomicBool::new(false);
46+
3247
/// A handle to a raw instance of the standard input stream of this process.
3348
///
3449
/// This handle is not synchronized or buffered in any fashion. Constructed via
@@ -890,10 +905,18 @@ impl fmt::Debug for StderrLock<'_> {
890905
#[doc(hidden)]
891906
pub fn set_panic(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write + Send>> {
892907
use crate::mem;
893-
LOCAL_STDERR.with(move |slot| mem::replace(&mut *slot.borrow_mut(), sink)).and_then(|mut s| {
894-
let _ = s.flush();
895-
Some(s)
896-
})
908+
if sink.is_none() && !LOCAL_STREAMS.load(Ordering::Relaxed) {
909+
// LOCAL_STDERR is definitely None since LOCAL_STREAMS is false.
910+
return None;
911+
}
912+
let s = LOCAL_STDERR.with(move |slot| mem::replace(&mut *slot.borrow_mut(), sink)).and_then(
913+
|mut s| {
914+
let _ = s.flush();
915+
Some(s)
916+
},
917+
);
918+
LOCAL_STREAMS.store(true, Ordering::Relaxed);
919+
s
897920
}
898921

899922
/// Resets the thread-local stdout handle to the specified writer
@@ -913,10 +936,18 @@ pub fn set_panic(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write +
913936
#[doc(hidden)]
914937
pub fn set_print(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write + Send>> {
915938
use crate::mem;
916-
LOCAL_STDOUT.with(move |slot| mem::replace(&mut *slot.borrow_mut(), sink)).and_then(|mut s| {
917-
let _ = s.flush();
918-
Some(s)
919-
})
939+
if sink.is_none() && !LOCAL_STREAMS.load(Ordering::Relaxed) {
940+
// LOCAL_STDOUT is definitely None since LOCAL_STREAMS is false.
941+
return None;
942+
}
943+
let s = LOCAL_STDOUT.with(move |slot| mem::replace(&mut *slot.borrow_mut(), sink)).and_then(
944+
|mut s| {
945+
let _ = s.flush();
946+
Some(s)
947+
},
948+
);
949+
LOCAL_STREAMS.store(true, Ordering::Relaxed);
950+
s
920951
}
921952

922953
/// Write `args` to output stream `local_s` if possible, `global_s`
@@ -937,20 +968,26 @@ fn print_to<T>(
937968
) where
938969
T: Write,
939970
{
940-
let result = local_s
941-
.try_with(|s| {
942-
// Note that we completely remove a local sink to write to in case
943-
// our printing recursively panics/prints, so the recursive
944-
// panic/print goes to the global sink instead of our local sink.
945-
let prev = s.borrow_mut().take();
946-
if let Some(mut w) = prev {
947-
let result = w.write_fmt(args);
948-
*s.borrow_mut() = Some(w);
949-
return result;
950-
}
951-
global_s().write_fmt(args)
971+
let result = LOCAL_STREAMS
972+
.load(Ordering::Relaxed)
973+
.then(|| {
974+
local_s
975+
.try_with(|s| {
976+
// Note that we completely remove a local sink to write to in case
977+
// our printing recursively panics/prints, so the recursive
978+
// panic/print goes to the global sink instead of our local sink.
979+
let prev = s.borrow_mut().take();
980+
if let Some(mut w) = prev {
981+
let result = w.write_fmt(args);
982+
*s.borrow_mut() = Some(w);
983+
return result;
984+
}
985+
global_s().write_fmt(args)
986+
})
987+
.ok()
952988
})
953-
.unwrap_or_else(|_| global_s().write_fmt(args));
989+
.flatten()
990+
.unwrap_or_else(|| global_s().write_fmt(args));
954991

955992
if let Err(e) = result {
956993
panic!("failed printing to {}: {}", label, e);

library/std/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@
226226
#![feature(asm)]
227227
#![feature(associated_type_bounds)]
228228
#![feature(atomic_mut_ptr)]
229+
#![feature(bool_to_option)]
229230
#![feature(box_syntax)]
230231
#![feature(c_variadic)]
231232
#![feature(cfg_accessible)]

0 commit comments

Comments
 (0)