Skip to content

Commit a426fc3

Browse files
committed
Auto merge of #85391 - Mark-Simulacrum:opt-tostring, r=scottmcm
Avoid zero-length memcpy in formatting This has two separate and somewhat orthogonal commits. The first change adjusts the ToString general impl for all types that implement Display; it no longer uses the full format machinery, rather directly falling onto a `std::fmt::Display::fmt` call. The second change directly adjusts the general core::fmt::write function which handles the production of format_args! to avoid zero-length push_str calls. Both changes target the fact that push_str will still call memmove internally (or a similar function), as it doesn't know the length of the passed string. For zero-length strings in particular, this is quite expensive, and even for very short (several bytes long) strings, this is also expensive. Future work in this area may wish to have us fallback to write_char or similar, which may be cheaper on the (typically) short strings between the interpolated pieces in format_args!.
2 parents df70463 + c7c9336 commit a426fc3

File tree

2 files changed

+32
-13
lines changed

2 files changed

+32
-13
lines changed

library/alloc/src/string.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -2323,9 +2323,10 @@ impl<T: fmt::Display + ?Sized> ToString for T {
23232323
// to try to remove it.
23242324
#[inline]
23252325
default fn to_string(&self) -> String {
2326-
use fmt::Write;
23272326
let mut buf = String::new();
2328-
buf.write_fmt(format_args!("{}", self))
2327+
let mut formatter = core::fmt::Formatter::new(&mut buf);
2328+
// Bypass format_args!() to avoid write_str with zero-length strs
2329+
fmt::Display::fmt(self, &mut formatter)
23292330
.expect("a Display implementation returned an error unexpectedly");
23302331
buf
23312332
}

library/core/src/fmt/mod.rs

+29-11
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,28 @@ pub struct Formatter<'a> {
221221
buf: &'a mut (dyn Write + 'a),
222222
}
223223

224+
impl<'a> Formatter<'a> {
225+
/// Creates a new formatter with default settings.
226+
///
227+
/// This can be used as a micro-optimization in cases where a full `Arguments`
228+
/// structure (as created by `format_args!`) is not necessary; `Arguments`
229+
/// is a little more expensive to use in simple formatting scenarios.
230+
///
231+
/// Currently not intended for use outside of the standard library.
232+
#[unstable(feature = "fmt_internals", reason = "internal to standard library", issue = "none")]
233+
#[doc(hidden)]
234+
pub fn new(buf: &'a mut (dyn Write + 'a)) -> Formatter<'a> {
235+
Formatter {
236+
flags: 0,
237+
fill: ' ',
238+
align: rt::v1::Alignment::Unknown,
239+
width: None,
240+
precision: None,
241+
buf,
242+
}
243+
}
244+
}
245+
224246
// NB. Argument is essentially an optimized partially applied formatting function,
225247
// equivalent to `exists T.(&T, fn(&T, &mut Formatter<'_>) -> Result`.
226248

@@ -1075,22 +1097,16 @@ pub trait UpperExp {
10751097
/// [`write!`]: crate::write!
10761098
#[stable(feature = "rust1", since = "1.0.0")]
10771099
pub fn write(output: &mut dyn Write, args: Arguments<'_>) -> Result {
1078-
let mut formatter = Formatter {
1079-
flags: 0,
1080-
width: None,
1081-
precision: None,
1082-
buf: output,
1083-
align: rt::v1::Alignment::Unknown,
1084-
fill: ' ',
1085-
};
1086-
1100+
let mut formatter = Formatter::new(output);
10871101
let mut idx = 0;
10881102

10891103
match args.fmt {
10901104
None => {
10911105
// We can use default formatting parameters for all arguments.
10921106
for (arg, piece) in iter::zip(args.args, args.pieces) {
1093-
formatter.buf.write_str(*piece)?;
1107+
if !piece.is_empty() {
1108+
formatter.buf.write_str(*piece)?;
1109+
}
10941110
(arg.formatter)(arg.value, &mut formatter)?;
10951111
idx += 1;
10961112
}
@@ -1099,7 +1115,9 @@ pub fn write(output: &mut dyn Write, args: Arguments<'_>) -> Result {
10991115
// Every spec has a corresponding argument that is preceded by
11001116
// a string piece.
11011117
for (arg, piece) in iter::zip(fmt, args.pieces) {
1102-
formatter.buf.write_str(*piece)?;
1118+
if !piece.is_empty() {
1119+
formatter.buf.write_str(*piece)?;
1120+
}
11031121
// SAFETY: arg and args.args come from the same Arguments,
11041122
// which guarantees the indexes are always within bounds.
11051123
unsafe { run(&mut formatter, arg, &args.args) }?;

0 commit comments

Comments
 (0)