-
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
Implement insert for RingBuf #19569
Implement insert for RingBuf #19569
Conversation
e20a325
to
5932ac5
Compare
/// assert_eq!(Some(&11), buf.get(1)); | ||
/// ``` | ||
pub fn insert(&mut self, i: uint, t: T) { | ||
assert!(i <= self.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 assert should have a message. e.g. assert!(whatever, "index out of bounds");
5932ac5
to
e3e6e9a
Compare
match i { | ||
0 => self.push_front(t), | ||
_ => { | ||
if i == self.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.
You can replace the parent block with this if/else. e.g.
_ => if ... {
...
} else {
...
}
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.
Also actually maybe just don't have the match at all? just an if/else-if/else?
I'm also not clear if the extra branches are worth it at all in the general case.
I added documentation. Do you think this algorithm is good enough? The worst case could be pretty bad. If it would be better to try to copy the smallest number of elements, even if it takes multiple copies, I can code that up. |
Sorry I've been in and out all day, was randomly scanning this on my phone for the most part. I now have a couple hours free, so I can actually review the real logic. |
// [o A o o o o o o o o o o . . . . ] | ||
// T H | ||
// 3 [o I A o o o o o o o o o o . . . ] | ||
// B = E |
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.
Yeah I don't think this will be acceptable. It would be very surprising if inserting at index 1 was this expensive in this data structure. Especially in such a "random" way from the user's perspective. It's especially bad because this cost isn't at all amortized, and pretty easy to trigger. That is, RingBuf::new(); push_back(1); insert(1,1); insert(1,1); ... insert(1,1);
will take O(n^2)
time.
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.
There might be a magic ratio here. Something like the double copy is worth it if the single copy would move twice as much, but I dunno. For now I'd just pick the minimal, and we can dig into optimization later (unless you're into that sort of thing and want to nail it now).
The algorithm should have a worst case of moving len/2 - 1 elements. I did a bunch of debug printing to make sure my test hits all of the branches. I am going to look at this again later tonight to see if there's anywhere that I can make the code more concise. |
Awesome! I will try to review this in a couple days when I have the time. I'm super paranoid about collections code though, so I'd appreciate getting a second pair of eyes from @huonw @cgaebel or @csherratt on this. |
let contiguous = self.tail < self.head; | ||
|
||
if contiguous { // easy stuff | ||
let (src, dst, count) = if distance_to_tail < distance_to_head { |
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 think you have missed an edge case here. There is a case where adding to the ring buffer breaks the buffer into none-contiguous memory. In that case a single memory copy is not sufficient.
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.
Yep, you're correct.
In the case that head moves from being contiguous to non-contiguous, it will always only require one copy because where head is pointing does not contain an element.
In the case of tail moving from contiguous to non-contiguous, if the insert is not at 0 in the ringbuf then it should require two copies.
H I T
[. . . . . o o o o o o o A o o o]
T H
[o . . . . o o o o o o o I A o o]
M M M M
I will add a test case for this, and add the corner case.
I included commit e00a63d which has all of the prints I used when making sure I hit the right coverage. That should make it easier to review. |
let distance_to_head = self.len() - i; | ||
|
||
// If the buffer is contiguous and the distance to head is less than the distance to | ||
// tail then move head, and do one copy. |
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 would add a comma or colon after tail
@@ -649,6 +649,189 @@ impl<T> RingBuf<T> { | |||
unsafe { Some(self.buffer_read(head)) } | |||
} | |||
} | |||
|
|||
/// Inserts an element at position `i` within the ringbuf, shifting all | |||
/// elements after position `i` one position to the right. |
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 no longer true.
Well, it kinda is, but the subtlety should be documented.
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'm not sure what the subtlety is. To the user, it appears as if the element was inserted at 'i' (the index they passed in), and the element that was at 'i' is now at 'i+1'
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.
The fact that elements to the left may be shifted could be surprising. There's no hint to the implementation's unique efficiency in the docs.
This draft is so much nicer to read! \o/ |
|
||
let contiguous = self.tail < self.head; | ||
|
||
if contiguous { |
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.
Instead of this nested if-else structure, is it possible to use a single match on a triple? That might help decrease the indentation here, and make it look really nice when you split up the docs case-by-case.
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.
An if-elseif-else would be fine, no?
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.
Yeah probably. The nesting is just a little overkill for what's happening.
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.
Ohh sorry I get what you mean, now. Yeah that might be cool.
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.
Although this design makes the case "nesting" more clear.
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.
But they're not really nested. The outer and inner conditions could be inverted and there'd be no loss of readability.
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.
Fair enough. Case... grouping?
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.
Well matching on something like (contiguous, in_tail_half, distance_to_head < distance_to_tail)
seems like it'd go a long way.
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'm planning on doing what @cgaebel suggested
Yeah it looks a LOT nicer now. I'm done my pass. |
One last thing: the comment about perf guarantee seems to have been lost as some point. Should specify that this does at most O(min(n, n - i)) work. |
@gankro I think I covered everything. Not sure on the wording of the doc statement. Do you have some more comments? |
// [o A o o o o o . . . . . . o o o] | ||
// | ||
// H T | ||
// [I A o o o o o . . . . . o o o o] |
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.
Diagram still deceptive about work done.
Awesome! I think this is good to go now! Just need to squash the commits into one and we can merge it. :D |
e490716
to
40f28c7
Compare
Aww man that's some pretty code. Nicely done! |
Thanks a lot for persevering through our brutal review! 😄 Interested in implementing |
This is a first pass at insert on RingBuf. I tried to keep it as simple as possible. I'm not sure of the performance implications of doing one copy vs. copying multiple times but moving a smaller amount of memory. I chose to stick with one copy, even if the amount of memory I have to move is larger. I believe this is part of #18424 @gankro mentioned this was missing.
This is a first pass at insert on RingBuf. I tried to keep it as simple as possible. I'm not sure of the performance implications of doing one copy vs. copying multiple times but moving a smaller amount of memory. I chose to stick with one copy, even if the amount of memory I have to move is larger. I believe this is part of rust-lang#18424 @gankro mentioned this was missing.
This is a first pass at insert on RingBuf. I tried to keep it as simple as possible. I'm not sure of the performance implications of doing one copy vs. copying multiple times but moving a smaller amount of memory. I chose to stick with one copy, even if the amount of memory I have to move is larger.
I believe this is part of #18424
@gankro mentioned this was missing.