Skip to content

Commit 86d9ed6

Browse files
committed
Auto merge of #39356 - krdln:format-with-capacity, r=aturon
Use `String::with_capacity` in `format!` Add an `Arguments::estimated_capacity` to estimate the length of formatted text and use it in `std::fmt::format` as the initial capacity of the buffer. The capacity is calculated based on the literal parts of format string, see the details in the implementation. Some benches: ```rust empty: format!("{}", black_box("")) literal: format!("Literal") long: format!("Hello Hello Hello Hello, {}!", black_box("world")) long_rev: format!("{}, hello hello hello hello!", black_box("world")) long_rev_2: format!("{}{}, hello hello hello hello!", 1, black_box("world")) short: format!("Hello, {}!", black_box("world")) short_rev: format!("{}, hello!", black_box("world")) short_rev_2: format!("{}{}, hello!", 1, black_box("world")) surround: format!("aaaaa{}ccccc{}eeeee", black_box("bbbbb"), black_box("eeeee")) two_spaced: format!("{} {}", black_box("bbbbb"), black_box("eeeee")) worst_case: format!("{} a long piece...", black_box("and even longer argument. not sure why it has to be so long")) ``` ``` empty 25 28 3 12.00% literal 35 29 -6 -17.14% long 80 46 -34 -42.50% long_rev 79 45 -34 -43.04% long_rev_2 111 66 -45 -40.54% short 73 46 -27 -36.99% short_rev 74 76 2 2.70% short_rev_2 107 108 1 0.93% surround 142 65 -77 -54.23% two_spaced 111 115 4 3.60% worst_case 89 101 12 13.48% ```
2 parents aed6410 + 0267529 commit 86d9ed6

File tree

4 files changed

+39
-1
lines changed

4 files changed

+39
-1
lines changed

src/libcollections/fmt.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,8 @@ use string;
539539
/// [format!]: ../macro.format.html
540540
#[stable(feature = "rust1", since = "1.0.0")]
541541
pub fn format(args: Arguments) -> string::String {
542-
let mut output = string::String::new();
542+
let capacity = args.estimated_capacity();
543+
let mut output = string::String::with_capacity(capacity);
543544
let _ = output.write_fmt(args);
544545
output
545546
}

src/libcore/fmt/mod.rs

+26
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,32 @@ impl<'a> Arguments<'a> {
265265
args: args
266266
}
267267
}
268+
269+
/// Estimates the length of the formatted text.
270+
///
271+
/// This is intended to be used for setting initial `String` capacity
272+
/// when using `format!`. Note: this is neither the lower nor upper bound.
273+
#[doc(hidden)] #[inline]
274+
#[unstable(feature = "fmt_internals", reason = "internal to format_args!",
275+
issue = "0")]
276+
pub fn estimated_capacity(&self) -> usize {
277+
let pieces_length: usize = self.pieces.iter()
278+
.map(|x| x.len()).sum();
279+
280+
if self.args.is_empty() {
281+
pieces_length
282+
} else if self.pieces[0] == "" && pieces_length < 16 {
283+
// If the format string starts with an argument,
284+
// don't preallocate anything, unless length
285+
// of pieces is significant.
286+
0
287+
} else {
288+
// There are some arguments, so any additional push
289+
// will reallocate the string. To avoid that,
290+
// we're "pre-doubling" the capacity here.
291+
pieces_length.checked_mul(2).unwrap_or(0)
292+
}
293+
}
268294
}
269295

270296
/// This structure represents a safely precompiled version of a format string

src/libcoretest/fmt/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,13 @@ fn test_pointer_formats_data_pointer() {
2828
assert_eq!(format!("{:p}", s), format!("{:p}", s.as_ptr()));
2929
assert_eq!(format!("{:p}", b), format!("{:p}", b.as_ptr()));
3030
}
31+
32+
#[test]
33+
fn test_estimated_capacity() {
34+
assert_eq!(format_args!("").estimated_capacity(), 0);
35+
assert_eq!(format_args!("{}", "").estimated_capacity(), 0);
36+
assert_eq!(format_args!("Hello").estimated_capacity(), 5);
37+
assert_eq!(format_args!("Hello, {}!", "").estimated_capacity(), 16);
38+
assert_eq!(format_args!("{}, hello!", "World").estimated_capacity(), 0);
39+
assert_eq!(format_args!("{}. 16-bytes piece", "World").estimated_capacity(), 32);
40+
}

src/libcoretest/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#![feature(ordering_chaining)]
3535
#![feature(ptr_unaligned)]
3636
#![feature(move_cell)]
37+
#![feature(fmt_internals)]
3738

3839
extern crate core;
3940
extern crate test;

0 commit comments

Comments
 (0)