-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
write!(wr,"foo") is 10% to 72% slower than wr.write("foo".as_bytes()) #10761
Comments
I suspect that #[inline(never)]
fn writer_write(w: &mut Writer, b: &[u8]) {
w.write(b);
}
#[bench]
fn bench_write_virt(bh: &mut BenchHarness) {
bh.iter(|| {
let mut mem = MemWriter::new();
let wr = &mut mem as &mut Writer;
for _ in range(0, 1000) {
writer_write(wr, "abc".as_bytes());
}
});
} I have, with
So, the virtual call have an impact, but it does not explain totally the slowness of |
Visiting for bug triage. This seems to still be present. The code to run the benchmarks is now:
Without optimizations:
With
|
Still an issue, using the following code: #![allow(unused_must_use)]
#![feature(test)]
extern crate test;
use std::io::Write;
use std::vec::Vec;
use test::Bencher;
#[bench]
fn bench_write_value(bh: &mut Bencher) {
bh.iter(|| {
let mut mem = Vec::new();
for _ in 0..1000 {
mem.write("abc".as_bytes());
}
});
}
#[bench]
fn bench_write_ref(bh: &mut Bencher) {
bh.iter(|| {
let mut mem = Vec::new();
let wr = &mut mem as &mut Write;
for _ in 0..1000 {
wr.write("abc".as_bytes());
}
});
}
#[bench]
fn bench_write_macro1(bh: &mut Bencher) {
bh.iter(|| {
let mut mem = Vec::new();
let wr = &mut mem as &mut Write;
for _ in 0..1000 {
write!(wr, "abc");
}
});
}
#[bench]
fn bench_write_macro2(bh: &mut Bencher) {
bh.iter(|| {
let mut mem = Vec::new();
let wr = &mut mem as &mut Write;
for _ in 0..1000 {
write!(wr, "{}", "abc");
}
});
} Typically giving something like the following:
|
`format_args!` uses one string literal per used `{}`, in many cases, the separating string pieces are empty. For example, `write!(w, "{}", x);` will attempt to write one empty string before the `{}` format, and `write!(w, "{}{}{}", x, y, z);" will write three empty string pieces between formats. Simply skip writing if the string is empty. It is a cheap branch compared to the virtual Write::write_str call that it makes possible to skip. It does not solve issue rust-lang#10761 in any way, yet that's where I noticed this. The testcase in the issue shows a performance difference between `write!(w, "abc")` and `write!(w, "{}", "abc")`, and this change halves the size of the difference.
I got approximately the same results with rust nightly. |
Just for the record, running with
|
Today:
|
Small perf improvement for fmt Added benchmark is based on rust-lang#10761
Small perf improvement for fmt Added benchmark is based on rust-lang#10761
Small perf improvement for fmt Added benchmark is based on rust-lang#10761
Two years later:
|
I was curious if the difference was in the implementation of io::Write vs. fmt::Write, or in the details of write_fmt requiring format_args!, which is relevant since write! can be asked to "serve" either case. To discover more information on this, I compared Vec to String, a seemingly "apples to apples...ish" comparison. https://gist.github.com/workingjubilee/2d2e3a7fded1c2101aafb51dc79a7ec5
|
I found it interesting that this example revealed that String actually had less overhead than Vec on Then I looked and saw: fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()> {
// Create a shim which translates an io::Write to a fmt::Write and saves
// off I/O errors. instead of discarding them
struct Adaptor<'a, T: ?Sized + 'a> {
inner: &'a mut T,
error: Result<()>,
}
/* More code related to implementing and using the resulting shim,
* seemingly involving a lot of poking a reference at runtime??? */
} So I am no longer surprised. |
(checking oldest issues for fun and profit) Did |
…ykril feat: hide type inlay hints for initializations of closures ![hide_closure_initialization](https://user-images.githubusercontent.com/12008103/168470158-6cb77b18-068e-4431-a8b5-e2b22d50d263.gif) This PR adds an option to hide the inlay hints for `let IDENT_PAT = CLOSURE_EXPR;`, which is a somewhat common coding pattern. Currently the inlay hints for the assigned variable and the closure expression itself are both displayed, making it rather repetitive. In order to be consistent with closure return type hints, only closures with block bodies will be hid by this option. Personally I'd feel comfortable making it always enabled (or at least when closure return type hints are enabled), but considering the precedent set in rust-lang#10761, I introduced an off-by-default option for this. changelog feature: option to hide type inlay hints for initializations of closures
Not really (
|
It seems to be just because nobody bothered to yet. https://gist.github.com/CAD97/2922d3132777310c1e22564f6c6dabdf running 8 tests
test new_string_write_macro1 ... bench: 1,875 ns/iter (+/- 228)
test new_string_write_macro2 ... bench: 13,594 ns/iter (+/- 999)
test new_vec_write_macro1 ... bench: 1,637 ns/iter (+/- 175)
test new_vec_write_macro2 ... bench: 13,808 ns/iter (+/- 1,320)
test old_string_write_macro1 ... bench: 10,080 ns/iter (+/- 1,618)
test old_string_write_macro2 ... bench: 13,880 ns/iter (+/- 1,327)
test old_vec_write_macro1 ... bench: 10,037 ns/iter (+/- 779)
test old_vec_write_macro2 ... bench: 14,783 ns/iter (+/- 1,280) --- old/src/std/io/mod.rs
+++ new/src/std/io/mod.rs
@@ -1650,8 +1650,12 @@
#[stable(feature = "rust1", since = "1.0.0")]
fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()> {
+ if let Some(s) = fmt.as_str() {
+ return self.write_all(s.as_bytes());
+ }
+
// Create a shim which translates a Write to a fmt::Write and saves
// off I/O errors. instead of discarding them
struct Adapter<'a, T: ?Sized + 'a> {
inner: &'a mut T,
error: Result<()>,
}
--- old/src/core/fmt/mod.rs
+++ new/src/core/fmt/mod.rs
@@ -186,4 +186,8 @@
#[stable(feature = "rust1", since = "1.0.0")]
fn write_fmt(mut self: &mut Self, args: Arguments<'_>) -> Result {
+ if let Some(s) = args.as_str() {
+ self.write_str(s)
+ } else {
- write(&mut self, args)
+ write(&mut self, args)
+ }
} |
It doesn't solve all the cases, but this definitely sounds like a nice improvement, that could be done in parallel with the ongoing |
Ignore `borrow_deref_ref` warnings in code from procedural macros. Don't linting `borrow_deref_ref` if code was generated by procedural macro. This PR fixes rust-lang/rust-clippy#8971 changelog: [`borrow_deref_ref`] avoiding warnings in macro-generated code
ten years later:
|
The macro1 and macro2 versions should be mostly identical now, thanks to format args flattening. Sadly, it's still not as fast as the non-macro version. Mara has recently posted an awesome blog post, where she explains the different trade-offs of format args macros. It's not practical to perfectly expand the macros everywhere, because while it could improve runtime performance, it could also hurt compile times and binary size a lot. That being said, in this simple case, where only a string is being written and only |
perf: improve write_fmt to handle simple strings Per `@dtolnay` suggestion in serde-rs/serde#2697 (comment) - attempt to speed up performance in the cases of a simple string format without arguments: ```rust write!(f, "text") -> f.write_str("text") ``` ```diff + #[inline] pub fn write_fmt(&mut self, f: fmt::Arguments) -> fmt::Result { + if let Some(s) = f.as_str() { + self.buf.write_str(s) + } else { write(self.buf, f) + } } ``` * Hopefully it will improve the simple case for the rust-lang#99012 * Another related (original?) issues rust-lang#10761 * Previous similar attempt to fix it by by `@Kobzol` rust-lang#100700 CC: `@m-ou-se` as probably the biggest expert in everything `format!`
perf: improve write_fmt to handle simple strings In case format string has no arguments, simplify its implementation with a direct call to `output.write_str(value)`. This builds on `@dtolnay` original [suggestion](serde-rs/serde#2697 (comment)). This does not change any expectations because the original `fn write()` implementation calls `write_str` for parts of the format string. ```rust write!(f, "text") -> f.write_str("text") ``` ```diff /// [`write!`]: crate::write! +#[inline] #[stable(feature = "rust1", since = "1.0.0")] pub fn write(output: &mut dyn Write, args: Arguments<'_>) -> Result { + if let Some(s) = args.as_str() { output.write_str(s) } else { write_internal(output, args) } +} + +/// Actual implementation of the [`write`], but without the simple string optimization. +fn write_internal(output: &mut dyn Write, args: Arguments<'_>) -> Result { let mut formatter = Formatter::new(output); let mut idx = 0; ``` * Hopefully it will improve the simple case for the rust-lang#99012 * Another related (original?) issues rust-lang#10761 * Previous similar attempt to fix it by by `@Kobzol` rust-lang#100700 CC: `@m-ou-se` as probably the biggest expert in everything `format!`
perf: improve write_fmt to handle simple strings In case format string has no arguments, simplify its implementation with a direct call to `output.write_str(value)`. This builds on `@dtolnay` original [suggestion](serde-rs/serde#2697 (comment)). This does not change any expectations because the original `fn write()` implementation calls `write_str` for parts of the format string. ```rust write!(f, "text") -> f.write_str("text") ``` ```diff /// [`write!`]: crate::write! +#[inline] #[stable(feature = "rust1", since = "1.0.0")] pub fn write(output: &mut dyn Write, args: Arguments<'_>) -> Result { + if let Some(s) = args.as_str() { output.write_str(s) } else { write_internal(output, args) } +} + +/// Actual implementation of the [`write`], but without the simple string optimization. +fn write_internal(output: &mut dyn Write, args: Arguments<'_>) -> Result { let mut formatter = Formatter::new(output); let mut idx = 0; ``` * Hopefully it will improve the simple case for the rust-lang#99012 * Another related (original?) issues rust-lang#10761 * Previous similar attempt to fix it by by `@Kobzol` rust-lang#100700 CC: `@m-ou-se` as probably the biggest expert in everything `format!`
perf: improve write_fmt to handle simple strings In case format string has no arguments, simplify its implementation with a direct call to `output.write_str(value)`. This builds on `@dtolnay` original [suggestion](serde-rs/serde#2697 (comment)). This does not change any expectations because the original `fn write()` implementation calls `write_str` for parts of the format string. ```rust write!(f, "text") -> f.write_str("text") ``` ```diff /// [`write!`]: crate::write! +#[inline] #[stable(feature = "rust1", since = "1.0.0")] pub fn write(output: &mut dyn Write, args: Arguments<'_>) -> Result { + if let Some(s) = args.as_str() { output.write_str(s) } else { write_internal(output, args) } +} + +/// Actual implementation of the [`write`], but without the simple string optimization. +fn write_internal(output: &mut dyn Write, args: Arguments<'_>) -> Result { let mut formatter = Formatter::new(output); let mut idx = 0; ``` * Hopefully it will improve the simple case for the rust-lang#99012 * Another related (original?) issues rust-lang#10761 * Previous similar attempt to fix it by by `@Kobzol` rust-lang#100700 CC: `@m-ou-se` as probably the biggest expert in everything `format!`
This example demonstrates that the trivial case of
write!(wr, "foo")
is much slower than callingwr.write("foo".as_bytes())
:With no optimizations:
With
--opt-level=3
:Is there anything we can do to improve this? I can think of a couple options, but I bet there are more:
write!
to compile down intowr.write("foo".as_bytes())
. If we go this route, it'd be nice to also convert a series of strwrite!("foo {} {}", "bar", "baz")
.wr.write_str("foo")
. From what I understand, that's being blocked on String encoding/decoding and I/O #6164.write!
overhead. Are there functions that should be getting inlined that are not? My scatter shot attempt didn't get any results.The text was updated successfully, but these errors were encountered: