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

Remove use of Option<T> inside of RingBuf #18170

Closed
wants to merge 7 commits into from
Closed

Remove use of Option<T> inside of RingBuf #18170

wants to merge 7 commits into from

Conversation

ghost
Copy link

@ghost ghost commented Oct 19, 2014

Fix for task in Metabug #18009

This changes much of about how RingBuf functions. lo, nelts are replaced by a more traditional head andtail. The Vec<Option<T>> is replaced by a bare pointer that is managed by the RingBuf itself. This also expects the ring buffer to always be size that is a power of 2.

This change also includes a number of new tests to cover the some areas that could be of concern with manual memory management.

The benchmarks have been reworked since the old ones were benchmarking of the Ring buffers growth rather then the actual test.

Benchmark

Before:

test ringbuf::tests::bench_grow_1025                       ... bench:      9200 ns/iter (+/- 100)
test ringbuf::tests::bench_iter_1000                       ... bench:      1341 ns/iter (+/- 34)
test ringbuf::tests::bench_mut_iter_1000                   ... bench:      2250 ns/iter (+/- 44)
test ringbuf::tests::bench_new                             ... bench:        22 ns/iter (+/- 0)
test ringbuf::tests::bench_pop_100                         ... bench:       351 ns/iter (+/- 3)
test ringbuf::tests::bench_pop_front_100                   ... bench:      1169 ns/iter (+/- 61)
test ringbuf::tests::bench_push_back_100                   ... bench:       244 ns/iter (+/- 3)
test ringbuf::tests::bench_push_front_100                  ... bench:       246 ns/iter (+/- 5)

After:

test ringbuf::tests::bench_grow_1025                       ... bench:      4278 ns/iter (+/- 38)
test ringbuf::tests::bench_iter_1000                       ... bench:      1074 ns/iter (+/- 15)
test ringbuf::tests::bench_mut_iter_1000                   ... bench:      1074 ns/iter (+/- 7)
test ringbuf::tests::bench_new                             ... bench:        19 ns/iter (+/- 0)
test ringbuf::tests::bench_pop_100                         ... bench:       145 ns/iter (+/- 2)
test ringbuf::tests::bench_pop_front_100                   ... bench:       229 ns/iter (+/- 1)
test ringbuf::tests::bench_push_back_100                   ... bench:       178 ns/iter (+/- 0)
test ringbuf::tests::bench_push_front_100                  ... bench:       171 ns/iter (+/- 0)

@rust-highfive
Copy link
Collaborator

warning Warning warning

  • These commits modify unsafe code. Please review it carefully!

@Gankra
Copy link
Contributor

Gankra commented Oct 20, 2014

Amazing! Thanks so much for doing this! 😍

elts: Vec<Option<T>>
head: uint,
tail: uint,
len: uint,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this maybe be called cap instead?

@Gankra
Copy link
Contributor

Gankra commented Oct 20, 2014

I've gone over all of the changes now. A great first-draft, I look forward to the next one!

performance depends on how big the ring buffer grows during the
test. This is determined not by the test, but by how many iterations
the Bencher chooses to run.

This change should make the benchmarks more reliable.
-Require the buffer to be a power of 2, and use that knowledge
 to allow for cheap index calculations.
-Remove the use of a Vec<Option<T>> for storage and move to a
 unmanaged buffer.
-Add is_full() and use it to check push push_back
-use is_empty() for pop/pop_back
-read get and make it return Option<&T>
-change get_mut to return to Option<&mut T>
@ghost
Copy link
Author

ghost commented Oct 25, 2014

@gankro updated numbers

Before

test ringbuf::tests::bench_grow_1025                       ... bench:      9317 ns/iter (+/- 126)
test ringbuf::tests::bench_iter_1000                       ... bench:      1343 ns/iter (+/- 19)
test ringbuf::tests::bench_mut_iter_1000                   ... bench:      1087 ns/iter (+/- 11)
test ringbuf::tests::bench_new                             ... bench:        22 ns/iter (+/- 0)
test ringbuf::tests::bench_pop_100                         ... bench:       349 ns/iter (+/- 4)
test ringbuf::tests::bench_pop_front_100                   ... bench:      1087 ns/iter (+/- 6)
test ringbuf::tests::bench_push_back_100                   ... bench:       219 ns/iter (+/- 2)
test ringbuf::tests::bench_push_front_100                  ... bench:       202 ns/iter (+/- 3)
test btree::map::bench::iter_1000                          ... bench:     15161 ns/iter (+/- 652)
test btree::map::bench::iter_100000                        ... bench:   1534378 ns/iter (+/- 53157)
test btree::map::bench::iter_20                            ... bench:       373 ns/iter (+/- 16)

Rework

test ringbuf::tests::bench_grow_1025                       ... bench:      4278 ns/iter (+/- 38)
test ringbuf::tests::bench_iter_1000                       ... bench:      1074 ns/iter (+/- 15)
test ringbuf::tests::bench_mut_iter_1000                   ... bench:      1074 ns/iter (+/- 7)
test ringbuf::tests::bench_new                             ... bench:        19 ns/iter (+/- 0)
test ringbuf::tests::bench_pop_100                         ... bench:       145 ns/iter (+/- 2)
test ringbuf::tests::bench_pop_front_100                   ... bench:       229 ns/iter (+/- 1)
test ringbuf::tests::bench_push_back_100                   ... bench:       178 ns/iter (+/- 0)
test ringbuf::tests::bench_push_front_100                  ... bench:       171 ns/iter (+/- 0)
test btree::map::bench::iter_1000                          ... bench:     14251 ns/iter (+/- 335)
test btree::map::bench::iter_100000                        ... bench:   1463265 ns/iter (+/- 27330)
test btree::map::bench::iter_20                            ... bench:       317 ns/iter (+/- 11)

Head/Tail

test ringbuf::tests::bench_grow_1025                       ... bench:      2997 ns/iter (+/- 138)
test ringbuf::tests::bench_iter_1000                       ... bench:       629 ns/iter (+/- 14)
test ringbuf::tests::bench_mut_iter_1000                   ... bench:       634 ns/iter (+/- 8)
test ringbuf::tests::bench_new                             ... bench:        15 ns/iter (+/- 0)
test ringbuf::tests::bench_pop_100                         ... bench:       219 ns/iter (+/- 2)
test ringbuf::tests::bench_pop_front_100                   ... bench:       217 ns/iter (+/- 2)
test ringbuf::tests::bench_push_back_100                   ... bench:       231 ns/iter (+/- 2)
test ringbuf::tests::bench_push_front_100                  ... bench:       191 ns/iter (+/- 2)
test btree::map::bench::iter_1000                          ... bench:     12926 ns/iter (+/- 477)
test btree::map::bench::iter_100000                        ... bench:   1327256 ns/iter (+/- 25008)
test btree::map::bench::iter_20                            ... bench:       289 ns/iter (+/- 9)

@Gankra
Copy link
Contributor

Gankra commented Oct 25, 2014

@csherratt Sorry, what commits are "rework" and "head/tail"?

@ghost
Copy link
Author

ghost commented Oct 25, 2014

@gankro rework is what I have now. Which is just the minimum rework to drop Option<T>. The Head/Tail changes include everything that I had done before, and some extra cleanup that you had suggested.

@Gankra
Copy link
Contributor

Gankra commented Oct 25, 2014

@csherratt Ah, I see. I'm uncertain about the value of this half-measure refactor, considering you had a fully-reviewed patch with much better perf ready to go? Admittedly it is much simpler...

@ghost
Copy link
Author

ghost commented Oct 25, 2014

@gankro I just want to get the change into master. I'd rather do smaller iterative changes since they are easier to review and measure their impact on the code base. I will do a follow-up change that will remove some of the needless bounds checking that you suggested as part of the first review.

@Gankra
Copy link
Contributor

Gankra commented Oct 25, 2014

Would the follow-up be the old set of patches? Because that would basically overwrite all these changes, right?

@ghost
Copy link
Author

ghost commented Oct 25, 2014

I was hoping someone would give a strong option on how to best attack this. The two approaches offer a bit of a tradeoff. One allows for the entire buffer to be used, and none-power-of-two buffers. The other allows for faster index calculations. It's a trade off and one that I don't actually know which one is better overall.

The other change is still here:
https://github.com/csherratt/rust/compare/ringbuf-remove-option-head-tail

If you think we should go forward with the other set of changes, then ok we can do that. If you think the advantages of having flexible buffer sizes is worth it, then we should use this change and back port all the cleanup we did in the other change in a follow-up PR.

@Gankra
Copy link
Contributor

Gankra commented Oct 25, 2014

I'm also willing to punt this issue to a more experienced developer. I'm never certain what tradeoffs we consider acceptable (this seems to be a moving target).

@emberian
Copy link
Member

Personally, I think the tradeoff should be towards speed here. Noone wants a slow ringbuf, even if it "wastes" some space.

@reem
Copy link
Contributor

reem commented Oct 25, 2014

I agree with @cmr, a few extra slots is a small price to pay for more speed in this case.

-Use unsafe for get()/get_mut()
-Correct some comment language.
-Rearrange the internal functions from external functions
Some(self.elts[raw_index].as_ref().unwrap())
let tail = self.tail;
self.tail = wrap_index(self.tail + 1, self.ring.len());
unsafe { Some(self.ring.unsafe_get(tail)) }
Copy link
Contributor

Choose a reason for hiding this comment

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

N.B.: Iteration could use the branchless pointer wrapping like HashMap does in table::Bucket::next. I'd like to see benchmarks if you choose to do it.

Copy link
Author

Choose a reason for hiding this comment

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

I gave the technique used in table::Bucket::next a try and was surprised to find it is significantly slower. There could be a flaw in how I implemented it.

Using the wrap_index function returns a result of 634ns, where the technique used in table::Bucket::next return a result of 1082ns.

pub struct MutItems<'a, T:'a> {
    headptr: *mut T,
    tailptr: *mut T,
    tail: uint,
    head: uint,
    cap: uint,
    marker: marker::ContravariantLifetime<'a>,
    marker2: marker::NoCopy
}
#[inline]
fn next(&mut self) -> Option<&'a mut T> {
    if self.headptr == self.tailptr {
        return None;
    }

    let maybe_wraparound_dist = (self.tail ^ (self.tail + 1)) & self.cap;
    // Finally, we obtain the offset 1 or the offset -cap + 1.
    let dist = 1i - (maybe_wraparound_dist as int);
    self.tail += 1;
    let ptr = self.tailptr;

    unsafe {
        self.tailptr = self.tailptr.offset(dist);
        Some(&mut *ptr)
    }
}

@Gankra
Copy link
Contributor

Gankra commented Oct 31, 2014

Ack! So many pending collections PRs! @csherratt Collections Reform just got approved, so everything's getting renamed and kicked around by like 4 people in parallel. You've already got some merge conflicts, and they're only going to get worse and worse. I'm really sorry this happened!

Mostly you'll just have to move trait impls to inherent impls, and tweak names. It might be easier to make the API refactors for DList part of this PR. Does that work for you?

@ghost ghost closed this Oct 31, 2014
@ghost
Copy link
Author

ghost commented Oct 31, 2014

I'd rather not mangle the commits. This is not a priority change so I can rebase it after you guys rework everything.

@Gankra
Copy link
Contributor

Gankra commented Nov 7, 2014

This work should be safe to start up again. RingBuf shouldn't get poked again. Or rather, it should be poked precisely for the changes you're making.

@ghost ghost mentioned this pull request Nov 7, 2014
self.elts.reserve(n);
// +1 since the buffer needs one more space then the expected size.
let count = num::next_power_of_two(n + 1);
if count > self.cap {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, yep you're right, this isn't what we want. We want

if self.len() + n > self.cap {
  let count = num::next_power_of_two(n + 1);
  self.grow(count);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

No wait,

let new_len = self.len() + n;
if new_len > self.capacity() {
  assert!(new_len + 1 <= int::MAX, "capacity overflow"); // eh there's probably an off-by-one here
  let count = num::next_power_of_two(new_len + 1);
  self.grow(count);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Whoops, I entered the wrong PR 🐫

bors added a commit that referenced this pull request Nov 16, 2014
Fix for task in Metabug #18009 (Rebased version of #18170)

This changes much of about how RingBuf functions. `lo`, `nelts` are replaced by a more traditional `head` and`tail`. The `Vec<Option<T>>` is replaced by a bare pointer that is managed by the `RingBuf` itself. This also expects the ring buffer to always be size that is a power of 2.

This change also includes a number of new tests to cover the some areas that could be of concern with manual memory management.

The benchmarks have been reworked since the old ones were benchmarking of the Ring buffers growth rather then the actual test.

The unit test suite have been expanded, and exposed some bugs in `fn get()` and `fn get_mut()`

## Benchmark
**Before:**
```
test ring_buf::tests::bench_grow_1025                      ... bench:      8919 ns/iter (+/- 87)
test ring_buf::tests::bench_iter_1000                      ... bench:       924 ns/iter (+/- 28)
test ring_buf::tests::bench_mut_iter_1000                  ... bench:       918 ns/iter (+/- 6)
test ring_buf::tests::bench_new                            ... bench:        15 ns/iter (+/- 0)
test ring_buf::tests::bench_pop_100                        ... bench:       294 ns/iter (+/- 9)
test ring_buf::tests::bench_pop_front_100                  ... bench:       948 ns/iter (+/- 32)
test ring_buf::tests::bench_push_back_100                  ... bench:       291 ns/iter (+/- 16)
test ring_buf::tests::bench_push_front_100                 ... bench:       311 ns/iter (+/- 27
```
**After:**
```
test ring_buf::tests::bench_grow_1025                      ... bench:      2209 ns/iter (+/- 169)
test ring_buf::tests::bench_iter_1000                      ... bench:       534 ns/iter (+/- 27)
test ring_buf::tests::bench_mut_iter_1000                  ... bench:       515 ns/iter (+/- 28)
test ring_buf::tests::bench_new                            ... bench:        11 ns/iter (+/- 0)
test ring_buf::tests::bench_pop_100                        ... bench:       170 ns/iter (+/- 5)
test ring_buf::tests::bench_pop_front_100                  ... bench:       171 ns/iter (+/- 11)
test ring_buf::tests::bench_push_back_100                  ... bench:       172 ns/iter (+/- 13)
test ring_buf::tests::bench_push_front_100                 ... bench:       158 ns/iter (+/- 12)

```
lnicola pushed a commit to lnicola/rust that referenced this pull request Sep 25, 2024
…eases, r=lnicola

minor: Revert "internal: Disable GitHub releases for now"

Turns out, this wasn't needed for long.
This pull request was closed.
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.

10 participants