这个例子演示了write!(wr, "foo")
的简单情况比调用wr.write("foo".as_bytes())
慢得多:
extern mod extra;
use std::io::mem::MemWriter;
use extra::test::BenchHarness;
#[bench]
fn bench_write_value(bh: &mut BenchHarness) {
bh.iter(|| {
let mut mem = MemWriter::new();
for _ in range(0, 1000) {
mem.write("abc".as_bytes());
}
});
}
#[bench]
fn bench_write_ref(bh: &mut BenchHarness) {
bh.iter(|| {
let mut mem = MemWriter::new();
let wr = &mut mem as &mut Writer;
for _ in range(0, 1000) {
wr.write("abc".as_bytes());
}
});
}
#[bench]
fn bench_write_macro1(bh: &mut BenchHarness) {
bh.iter(|| {
let mut mem = MemWriter::new();
let wr = &mut mem as &mut Writer;
for _ in range(0, 1000) {
write!(wr, "abc");
}
});
}
#[bench]
fn bench_write_macro2(bh: &mut BenchHarness) {
bh.iter(|| {
let mut mem = MemWriter::new();
let wr = &mut mem as &mut Writer;
for _ in range(0, 1000) {
write!(wr, "{}", "abc");
}
});
}
没有优化:
running 4 tests
test bench_write_macro1 ... bench: 280153 ns/iter (+/- 73615)
test bench_write_macro2 ... bench: 322462 ns/iter (+/- 24886)
test bench_write_ref ... bench: 79974 ns/iter (+/- 3850)
test bench_write_value ... bench: 78709 ns/iter (+/- 4003)
test result: ok. 0 passed; 0 failed; 0 ignored; 4 measured
使用--opt-level=3
:
running 4 tests
test bench_write_macro1 ... bench: 62397 ns/iter (+/- 5485)
test bench_write_macro2 ... bench: 80203 ns/iter (+/- 3355)
test bench_write_ref ... bench: 55275 ns/iter (+/- 5156)
test bench_write_value ... bench: 56273 ns/iter (+/- 7591)
test result: ok. 0 passed; 0 failed; 0 ignored; 4 measured
我们可以做些什么来改善这种情况? 我可以想到几个选项,但我敢打赌还有更多:
write!
编译成wr.write("foo".as_bytes())
。 如果我们走这条路,最好还转换一系列 str write!("foo {} {}", "bar", "baz")
。wr.write_str("foo")
。 据我所知,这在#6164 上被阻止了。write!
开销。 有没有应该被内联的函数? 我的散弹尝试没有得到任何结果。我怀疑fn bench_write_ref
不会进行虚拟调用。 添加这个
#[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());
}
});
}
我有, --opt-level 3
running 5 tests
test bench_write_macro1 ... bench: 680823 ns/iter (+/- 34497)
test bench_write_macro2 ... bench: 950790 ns/iter (+/- 72309)
test bench_write_ref ... bench: 505846 ns/iter (+/- 41965)
test bench_write_value ... bench: 511815 ns/iter (+/- 36681)
test bench_write_virt ... bench: 553466 ns/iter (+/- 43716)
所以,虚拟调用有影响,但它并不能完全解释write!()
显示的这个板凳的缓慢。
访问错误分类。 这似乎仍然存在。 运行基准测试的代码现在是:
extern crate test;
use std::io::MemWriter;
use test::Bencher;
#[bench]
fn bench_write_value(bh: &mut Bencher) {
bh.iter(|| {
let mut mem = MemWriter::new();
for _ in range(0u, 1000) {
mem.write("abc".as_bytes());
}
});
}
#[bench]
fn bench_write_ref(bh: &mut Bencher) {
bh.iter(|| {
let mut mem = MemWriter::new();
let wr = &mut mem as &mut Writer;
for _ in range(0u, 1000) {
wr.write("abc".as_bytes());
}
});
}
#[bench]
fn bench_write_macro1(bh: &mut Bencher) {
bh.iter(|| {
let mut mem = MemWriter::new();
let wr = &mut mem as &mut Writer;
for _ in range(0u, 1000) {
write!(wr, "abc");
}
});
}
#[bench]
fn bench_write_macro2(bh: &mut Bencher) {
bh.iter(|| {
let mut mem = MemWriter::new();
let wr = &mut mem as &mut Writer;
for _ in range(0u, 1000) {
write!(wr, "{}", "abc");
}
});
}
没有优化:
running 4 tests
test bench_write_macro1 ... bench: 1470468 ns/iter (+/- 291966)
test bench_write_macro2 ... bench: 1799612 ns/iter (+/- 316293)
test bench_write_ref ... bench: 1336574 ns/iter (+/- 251664)
test bench_write_value ... bench: 1317880 ns/iter (+/- 254668)
test result: ok. 0 passed; 0 failed; 0 ignored; 4 measured
使用--opt-level=3
:
running 4 tests
test bench_write_macro1 ... bench: 127671 ns/iter (+/- 1452)
test bench_write_macro2 ... bench: 196158 ns/iter (+/- 2053)
test bench_write_ref ... bench: 43881 ns/iter (+/- 453)
test bench_write_value ... bench: 43859 ns/iter (+/- 336)
test result: ok. 0 passed; 0 failed; 0 ignored; 4 measured
仍然是一个问题,使用以下代码:
#![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");
}
});
}
通常给出如下内容:
$ cargo bench
running 4 tests
test bench_write_macro1 ... bench: 21,604 ns/iter (+/- 82)
test bench_write_macro2 ... bench: 29,273 ns/iter (+/- 85)
test bench_write_ref ... bench: 1,396 ns/iter (+/- 387)
test bench_write_value ... bench: 1,391 ns/iter (+/- 163)
我每晚都得到了与 Rust 大致相同的结果。
只是为了记录,运行v1.20.0-nightly
:
$ cargo bench
running 4 tests
test bench_write_macro1 ... bench: 36,556 ns/iter (+/- 69)
test bench_write_macro2 ... bench: 54,377 ns/iter (+/- 958)
test bench_write_ref ... bench: 13,730 ns/iter (+/- 24)
test bench_write_value ... bench: 13,755 ns/iter (+/- 81)
今天:
running 4 tests
test bench_write_macro1 ... bench: 16,220 ns/iter (+/- 982)
test bench_write_macro2 ... bench: 25,542 ns/iter (+/- 2,220)
test bench_write_ref ... bench: 4,889 ns/iter (+/- 314)
test bench_write_value ... bench: 4,819 ns/iter (+/- 956)
两年后:
running 4 tests
test bench_write_macro1 ... bench: 17,561 ns/iter (+/- 174)
test bench_write_macro2 ... bench: 23,285 ns/iter (+/- 2,771)
test bench_write_ref ... bench: 3,234 ns/iter (+/- 194)
test bench_write_value ... bench: 3,238 ns/iter (+/- 123)
test result: ok. 0 passed; 0 failed; 0 ignored; 4 measured; 0 filtered out
❯ rustc +nightly --version
rustc 1.47.0-nightly (30f0a0768 2020-08-18)
我很好奇区别是在 io::Write 与 fmt::Write 的实现上,还是在 write_fmt 需要 format_args! 的细节上,这与 write! 相关! 可以要求“服务”任何一种情况。
为了发现更多关于此的信息,我将 Vec 与 String 进行了比较,这是一个看似“苹果与苹果...... ish”的比较。 https://gist.github.com/workingjubilee/2d2e3a7fded1c2101aafb51dc79a7ec5
running 10 tests
test string_write_fmt ... bench: 10,053 ns/iter (+/- 1,141)
test string_write_macro1 ... bench: 10,177 ns/iter (+/- 2,363)
test string_write_macro2 ... bench: 17,499 ns/iter (+/- 1,847)
test string_write_ref ... bench: 2,270 ns/iter (+/- 265)
test string_write_value ... bench: 2,333 ns/iter (+/- 126)
test vec_write_fmt ... bench: 15,722 ns/iter (+/- 1,673)
test vec_write_macro1 ... bench: 15,767 ns/iter (+/- 1,638)
test vec_write_macro2 ... bench: 23,968 ns/iter (+/- 8,942)
test vec_write_ref ... bench: 2,296 ns/iter (+/- 178)
test vec_write_value ... bench: 2,230 ns/iter (+/- 235)
test result: ok. 0 passed; 0 failed; 0 ignored; 10 measured; 0 filtered out
我发现有趣的是,这个例子表明 String 在write!
上的开销实际上比 Vec 少(基准有很大的差异,但我认为它足够指示)。
然后我看了看:
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??? */
}
所以我不再感到惊讶。
我相信在某些情况下可以快速处理,但这实际上不是这里的一个苹果对苹果的比较。 橙色是write_fmt
,它必须使用各种格式化机制。 需要发生的是write!
在不需要格式化机制的简单情况下应该跳过write_fmt
,或者在明显的快速路径的情况下格式化机制本身应该快得多。