Skip to content
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

Merged
merged 1 commit into from
Dec 12, 2014
Merged

Implement insert for RingBuf #19569

merged 1 commit into from
Dec 12, 2014

Conversation

murphm8
Copy link
Contributor

@murphm8 murphm8 commented Dec 5, 2014

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.

/// assert_eq!(Some(&11), buf.get(1));
/// ```
pub fn insert(&mut self, i: uint, t: T) {
assert!(i <= self.len());
Copy link
Contributor

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");

match i {
0 => self.push_front(t),
_ => {
if i == self.len() {
Copy link
Contributor

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 {
  ... 
}

Copy link
Contributor

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.

@murphm8
Copy link
Contributor Author

murphm8 commented Dec 5, 2014

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.

@Gankra
Copy link
Contributor

Gankra commented Dec 5, 2014

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
Copy link
Contributor

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.

Copy link
Contributor

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).

@murphm8
Copy link
Contributor Author

murphm8 commented Dec 6, 2014

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.

@Gankra
Copy link
Contributor

Gankra commented Dec 7, 2014

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 {
Copy link

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.

Copy link
Contributor Author

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.

@murphm8
Copy link
Contributor Author

murphm8 commented Dec 7, 2014

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.
Copy link
Contributor

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.
Copy link
Contributor

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.

Copy link
Contributor Author

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'

Copy link
Contributor

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.

@Gankra
Copy link
Contributor

Gankra commented Dec 9, 2014

This draft is so much nicer to read! \o/


let contiguous = self.tail < self.head;

if contiguous {
Copy link
Contributor

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.

Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Case... grouping?

Copy link
Contributor

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.

Copy link
Contributor Author

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

@cgaebel
Copy link
Contributor

cgaebel commented Dec 9, 2014

Yeah it looks a LOT nicer now. I'm done my pass.

@Gankra
Copy link
Contributor

Gankra commented Dec 9, 2014

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.

@murphm8
Copy link
Contributor Author

murphm8 commented Dec 10, 2014

@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]
Copy link
Contributor

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.

@Gankra
Copy link
Contributor

Gankra commented Dec 12, 2014

Awesome! I think this is good to go now! Just need to squash the commits into one and we can merge it. :D

@cgaebel
Copy link
Contributor

cgaebel commented Dec 12, 2014

Aww man that's some pretty code. Nicely done!

@Gankra
Copy link
Contributor

Gankra commented Dec 12, 2014

Thanks a lot for persevering through our brutal review! 😄

Interested in implementing remove and shrink_to_fit? 😈

bors added a commit that referenced this pull request Dec 12, 2014
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.
brson added a commit to brson/rust that referenced this pull request Dec 12, 2014
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.
@bors bors closed this Dec 12, 2014
@bors bors merged commit 40f28c7 into rust-lang:master Dec 12, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants