-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
std: rename MemWriter to SeekableMemWriter, add seekless MemWriter #15915
Conversation
cc @cmr, who originally proposed this idea. |
Before we split the types, have you looked into optimizing fn write() {
if ever_seeked { /* slower write() */ }
else { self.buf.push_all(buf); Ok(()) }
} |
@alexcrichton: I forgot to mention I did some benchmarking of adding a hot path to the original MemWriter, but it didn't seem to do much:
Here are the results from the benchmarks with it:
The biggest wins I've found have been in my https://github.com/erickt/rust-serde project. I've been benchmarking my new json serializer against Go's builtin serializer, Go's ffjson and RapidJSON. Without my PR, or with this hot path, I go from:
to this:
So it's a pretty substantial win for me. In comparison, ffjson is about 120MB/s, and RapidJSON is at 230MB/s, so we're basically at parity with them without having to codegen serialization-specific code. |
I would prefer to duplicate those results before resulting to duplication of APIs, this does not seem like a pattern that we wish to encourage in the standard library. I checked out Is that benchmark part of a tweak which hasn't been uploaded yet? |
@alexcrichton: oops, I haven't pushed up the version with
Or you could look at bench_serializer2, which has a local copy of my MemWriter rewrite. |
Another possible alternative is to just remove the Seek impl from MemWriter. It's only used by the ebml serialization, and that can be restructured to not require Seek - which would also help shrink metadata a bit. |
@sfackler: that's a good point. I'll also look into that tonight. |
Ok, I was able to reproduce your results:
The serializer0 benchmark is MemWriter with no seek (write is just #[inline]
fn write(&mut self, buf: &[u8]) -> io::IoResult<()> {
if self.pos == uint::MAX {
self.buf.push_all(buf);
return Ok(())
}
self.write_slow(buf)
}
#[inline(never)] fn write_slow(...) { ... } Here the This tells me a few things:
I'm not sure if this is a case to hamstring I suppose the lease-painful route would be the fast/slow path, but it wouldn't get to the speed that you're looking at, but perhaps an internal buffer would get there? (unknown) |
I wonder if you can eek out a little more speed in the fast case with a |
It may have bought a bit, but it doesn't seem to have made a noticeable impact. |
@alexcrichton does any code outside of |
Not that I'm aware of, but one may logically expect memory-based readers/writers to be seekable. |
FWIW, Go and Java's equivalents don't seek, and I honestly can't say I've ever wanted to. |
I was chatting with @erickt on IRC last night, and if there's precedent for not having seek then perhaps it won't be so bad to remove them. Instead of adding |
I'm inclined to agree, memory writers don't need to be seekable. Their primary job is to buffer writes. If I need to seek, I would also think I may already know the total size of the buffer as well, at which point I would just use |
Could you move |
Not all users of MemWriter need to seek, but having MemWriter seekable adds between 3-29% in overhead in certain circumstances. This fixes that performance gap by making a non-seekable MemWriter, and creating a new SeekableMemWriter for those circumstances when that functionality is actually needed. ``` test io::mem::test::bench_buf_reader ... bench: 682 ns/iter (+/- 85) test io::mem::test::bench_buf_writer ... bench: 580 ns/iter (+/- 57) test io::mem::test::bench_mem_reader ... bench: 793 ns/iter (+/- 99) test io::mem::test::bench_mem_writer_001_0000 ... bench: 48 ns/iter (+/- 27) test io::mem::test::bench_mem_writer_001_0010 ... bench: 65 ns/iter (+/- 27) = 153 MB/s test io::mem::test::bench_mem_writer_001_0100 ... bench: 132 ns/iter (+/- 12) = 757 MB/s test io::mem::test::bench_mem_writer_001_1000 ... bench: 802 ns/iter (+/- 151) = 1246 MB/s test io::mem::test::bench_mem_writer_100_0000 ... bench: 481 ns/iter (+/- 28) test io::mem::test::bench_mem_writer_100_0010 ... bench: 1957 ns/iter (+/- 126) = 510 MB/s test io::mem::test::bench_mem_writer_100_0100 ... bench: 8222 ns/iter (+/- 434) = 1216 MB/s test io::mem::test::bench_mem_writer_100_1000 ... bench: 82496 ns/iter (+/- 11191) = 1212 MB/s test io::mem::test::bench_seekable_mem_writer_001_0000 ... bench: 48 ns/iter (+/- 2) test io::mem::test::bench_seekable_mem_writer_001_0010 ... bench: 64 ns/iter (+/- 2) = 156 MB/s test io::mem::test::bench_seekable_mem_writer_001_0100 ... bench: 129 ns/iter (+/- 7) = 775 MB/s test io::mem::test::bench_seekable_mem_writer_001_1000 ... bench: 801 ns/iter (+/- 159) = 1248 MB/s test io::mem::test::bench_seekable_mem_writer_100_0000 ... bench: 711 ns/iter (+/- 51) test io::mem::test::bench_seekable_mem_writer_100_0010 ... bench: 2532 ns/iter (+/- 227) = 394 MB/s test io::mem::test::bench_seekable_mem_writer_100_0100 ... bench: 8962 ns/iter (+/- 947) = 1115 MB/s test io::mem::test::bench_seekable_mem_writer_100_1000 ... bench: 85086 ns/iter (+/- 11555) = 1175 MB/s ``` [breaking-change]
@bors: retry |
std: rename MemWriter to SeekableMemWriter, add seekless MemWriter Not all users of MemWriter need to seek, but having MemWriter seekable adds between 3-29% in overhead in certain circumstances. This fixes that performance gap by making a non-seekable MemWriter, and creating a new SeekableMemWriter for those circumstances when that functionality is actually needed. ``` test io::mem::test::bench_buf_reader ... bench: 682 ns/iter (+/- 85) test io::mem::test::bench_buf_writer ... bench: 580 ns/iter (+/- 57) test io::mem::test::bench_mem_reader ... bench: 793 ns/iter (+/- 99) test io::mem::test::bench_mem_writer_001_0000 ... bench: 48 ns/iter (+/- 27) test io::mem::test::bench_mem_writer_001_0010 ... bench: 65 ns/iter (+/- 27) = 153 MB/s test io::mem::test::bench_mem_writer_001_0100 ... bench: 132 ns/iter (+/- 12) = 757 MB/s test io::mem::test::bench_mem_writer_001_1000 ... bench: 802 ns/iter (+/- 151) = 1246 MB/s test io::mem::test::bench_mem_writer_100_0000 ... bench: 481 ns/iter (+/- 28) test io::mem::test::bench_mem_writer_100_0010 ... bench: 1957 ns/iter (+/- 126) = 510 MB/s test io::mem::test::bench_mem_writer_100_0100 ... bench: 8222 ns/iter (+/- 434) = 1216 MB/s test io::mem::test::bench_mem_writer_100_1000 ... bench: 82496 ns/iter (+/- 11191) = 1212 MB/s test io::mem::test::bench_seekable_mem_writer_001_0000 ... bench: 48 ns/iter (+/- 2) test io::mem::test::bench_seekable_mem_writer_001_0010 ... bench: 64 ns/iter (+/- 2) = 156 MB/s test io::mem::test::bench_seekable_mem_writer_001_0100 ... bench: 129 ns/iter (+/- 7) = 775 MB/s test io::mem::test::bench_seekable_mem_writer_001_1000 ... bench: 801 ns/iter (+/- 159) = 1248 MB/s test io::mem::test::bench_seekable_mem_writer_100_0000 ... bench: 711 ns/iter (+/- 51) test io::mem::test::bench_seekable_mem_writer_100_0010 ... bench: 2532 ns/iter (+/- 227) = 394 MB/s test io::mem::test::bench_seekable_mem_writer_100_0100 ... bench: 8962 ns/iter (+/- 947) = 1115 MB/s test io::mem::test::bench_seekable_mem_writer_100_1000 ... bench: 85086 ns/iter (+/- 11555) = 1175 MB/s ```
Its seekability wasn’t being used, and so in light of the changes made in rust-lang/rust#15915 it should be removed, rather than switching from the now-unseekable ``MemWriter`` to the new ``SeekableMemWriter``. This necessitated adding the ``get_ref`` method which was added to ``MemWriter`` at some point.
std: rename MemWriter to SeekableMemWriter, add seekless MemWriter
Not all users of MemWriter need to seek, but having MemWriter seekable adds between 3-29% in overhead in certain circumstances. This fixes that performance gap by making a non-seekable MemWriter, and creating a new SeekableMemWriter for those circumstances when that functionality is actually needed.