この例は、の些細な場合ことを実証している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)
毎晩錆びてもほぼ同じ結果が得られました。
念のため、 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!から関連するformat_args!を必要とするwrite_fmtの詳細にあるのか興味がありました。 どちらの場合も「提供」するように依頼できます。
これに関する詳細情報を見つけるために、私はVecとStringを比較しました。これは、一見「リンゴとリンゴ...っぽい」比較です。 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
write!
をスキップするか、明らかな高速パスの場合はフォーマット機構自体がはるかに高速である必要があるということです。