-
Notifications
You must be signed in to change notification settings - Fork 109
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
Add EndianReader<T> #298
Add EndianReader<T> #298
Conversation
So far this looks great! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good. Once the Pin APIs stabilize (for immovable data), we can probably do a more efficient implementation that's closer to what bytes
does.
src/endian_reader.rs
Outdated
#[inline] | ||
fn read_u16(&mut self) -> Result<u16> { | ||
let slice = self.range.read_slice(2)?; | ||
Ok(self.endian.read_u16(slice)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it's worth adding default implementations to Reader
for these, to slightly reduce the burden for implementations of Reader
:
let slice: [u8; 2] = self.read_u8_array()?;
Ok(self.endian().read_u16(&slice))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can make it work, then totally. I think using read_u8_array
would avoid the borrow issues I was running into, since it makes a copy. The copy is of a small size, and I expect llvm should also optimize it away, but verifying that requires digging into the disassembly.
Thanks. I know you were also thinking about solving this issue; was the API you were imagining much different?
Can you expand a little more about how you expect |
My initial thoughts were along the lines of
Sorry, that was based on expecting |
Thanks for the clarification!
Ok.
Sort of. A self-reference is safe as long as the underlying thing is pinned. I think it gives us a vocabulary for talking about this stuff, but not any off-the-shelf solutions for self-reference, if that makes sense. I also haven't read the RFC since it underwent a couple changes, so take this with a grain of salt.
I think we can replace start/end with ptr/len and some unsafe code without changing the public interface, assumign we see this show up in profiles. |
To expand a little here, there would still be ref counting, but instead of doing fn slice(&self) -> &[u8] {
bounds_check(*(arc).len, start)
bounds_check(*(arc).len, end)
(*(arc.ptr + start), end - start)
}
fn clone(&self) -> Self {
arc.increment_ref_count()
Self {
arc: self.arc,
start: self.start,
end: self.end,
}
} we would do fn slice(&self) -> &[u8] {
(self.ptr, self.len)
}
fn clone(&self) -> Self {
// Still need to keep the original thing alive.
self.arc.increment_ref_count();
Self {
arc: self.arc,
ptr: self.ptr,
len: self.len,
}
} Although, come to think of it, I am unsure that this optimization would be valid with arbitrary |
Yes, I should have mentioned that there's https://crates.io/crates/stable_deref_trait for that, I had just thought that |
I think this is going to be useful for converting |
Picking this back up now. |
@philipc ok, I think this is ready for review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of things to fix.
Also I checked the performance for bench_parsing_debug_info
and EndianRcSlice
is about 50% slower than EndianSlice
.
/// ``` | ||
/// use std::rc::Rc; | ||
/// | ||
/// let buf = Rc::from(&[1, 2, 3, 4][..]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way that EndianRcSlice
and EndianArcSlice
will be usable without allocating a copy of the slice like this does?
If you already have an allocation (eg a Vec
or Cow
) and you want to avoid a copy, then it looks like you need to use Rc<Vec<u8>>
instead, but accessing the slice in that requires an extra deref.
Not sure there is anything we can do better here, it just wasn't obvious to me and I hadn't thought about it before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you think about it, this is pretty inherent to the operation you're doing, since to avoid the extra deref, you need to put the refcount right before the heap elements, but if you're using a Vec
, that means you would have needed to have already reserved that space. This is why you can turn a Vec<u8>
into a Box<[u8]>
with zero cost (because it is a fat pointer of (ptr, len)
) but there is not a zero cost Vec<u8>
into Rc<[u8]>
.
It wouldn't be difficult to write a custom type that puts the refcount in front of the heap elements, but it would be a bunch of boilerplate and some unsafe code.
/// Say you have an `mmap`ed file that you want to serve as a `gimli::Reader`. | ||
/// You can wrap that `mmap`ed file up in a `MmapFile` type and use | ||
/// `EndianReader<Rc<MmapFile>>` or `EndianReader<Arc<MmapFile>>` as readers as | ||
/// long as `MmapFile` dereferences to the underlying `[u8]` data. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a mmap
of the whole file, not the sections, so if we wanted to use this example in practice, we'd need our object parser to tell us the offsets of the sections, or subtract pointers to recover the offsets, or have an object parser that uses gimli::Reader
. Nothing to do in this PR, just more stuff I hadn't thought about before.
src/endian_reader.rs
Outdated
} else { | ||
let start = self.start; | ||
self.start += len; | ||
Ok(&self.bytes[start..self.end]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be self.bytes[start..][..len]
.
src/endian_reader.rs
Outdated
// let buf = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; | ||
// let eb = EndianReader::new(&buf, NativeEndian); | ||
// eb.split_at(30); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests? I know the rest of the Reader
stuff is lacking in unit tests, but I'd be happy with copying one of the tests in tests/parse_self.rs
to use EndianRcSlice
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally. I meant to do this and completely forgot. Need to hold myself to a higher standard -_-
src/endian_reader.rs
Outdated
if self.len() < len { | ||
Err(Error::UnexpectedEof) | ||
} else { | ||
self.range.start = self.range.end - len; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be self.range.end = self.range.start + len;
.
An easy way to define a custom `Reader` implementation with a reference to a generic buffer of bytes and an associated endianity. Fix gimli-rs#294
Looks like it takes about 1.58x the time that using `EndianSlice` does: ``` test bench_parsing_debug_info ... bench: 2,261,953 ns/iter (+/- 142,827) test bench_parsing_debug_info_with_endian_rc_slice ... bench: 3,564,292 ns/iter (+/- 208,038) ```
@philipc ok, I added unit tests for As far as benchmark timing goes, my findings were similar to yours, where using |
Thanks! |
This is a work-in-progress / experimental approach to #294
I don't particularly care if this is the approach we end up taking, but I wanted to try making a generic thing that would work with
Rc<[u8]>
andArc<[u8]>
and more generally anyT: Deref<[u8]> + Clone
. The idea being thatT
might be some custommmap
type.In particular, check out the table in the
gimli::Reader
doc comment in addition to theEndianReader<T>
definition.Thoughts @philipc, @koute?