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 Unsafe Blocks #195

Open
bheisler opened this issue Aug 27, 2018 · 8 comments
Open

Remove Unsafe Blocks #195

bheisler opened this issue Aug 27, 2018 · 8 comments
Labels
Beginner Suitable for people new to Criterion.rs Enhancement

Comments

@bheisler
Copy link
Owner

There are a lot of old unsafe blocks in Criterion.rs' internals. Many of them were written to work around compiler bugs or missing features that have since been fixed (see #188 (comment)). These should be cleaned up.

There are a few others that might be harder to remove without impacting analysis performance in the bootstrapping code. I've tried several times to remove these without much success so far.

@bheisler bheisler added the Beginner Suitable for people new to Criterion.rs label Aug 27, 2018
@gnzlbg gnzlbg mentioned this issue Sep 20, 2018
@gnzlbg
Copy link
Contributor

gnzlbg commented Sep 20, 2018

I managed to remove many unsafe blocks in #208 . Most of the remaining ones are due to the usage of thread_scoped, here we should migrate criterion either to crossbeam::scope, or maybe even just rayon. cc @cuviper thoughts?

The remaining unsafe blocks are only in the stats crate and hint about the crate's problematic design. For example, some transmutes are just used to workaround some types not having new methods (to subvert privacy boundaries), others are also used to subvert the type system in weird ways: for example, Sample<A: Float> but Distribution<A> does not require A: Float, yet distributions with As that are not floats are transmuted into Samples.

@cuviper
Copy link

cuviper commented Sep 20, 2018

or maybe even just rayon. cc @cuviper thoughts?

How could I say no? 😉 But yes, indeed, those look like prime candidates for parallel iterators.

If you wanted to get fancy, those Builders could probably implement FromParallelIterator to directly fill their target vectors, but it's probably good enough to leave it with sub_distributions and the extend merging at first.

@bheisler
Copy link
Owner Author

I've tried using Rayon there, in fact. It was a while ago, though, so it might work better now. The problem I ran into is that, well, some of that code is super complex (I'm looking at mixed.rs here). There is a thread-local structure (Resamples) which holds a borrow of the shared vector c. I couldn't figure out a way to do that in Rayon without creating a new Resamples instance for every iteration (potentially expensive because it contains a random number generator as well, which needs to be initialized from the thread_rng()) or crossing the borrow checker or both. I believe I also tried to implement FromParallelIterator for the Builders and gave up eventually, though I don't remember why. The other obvious approach - using split_at_mut and giving each chunk to a different scoped thread - also doesn't work, because of the way it tries to be generic over the number of output vectors.

Anyway, perhaps you folks will have more success than I did. I always meant to rip that code out and replace it with something more maintainable, but I didn't want to make it much slower. I think I might have to compromise on the performance (accept the cost of creating a new RNG for each iteration) or the generality (stop trying to be generic over the number of outputs and unzip them explicitly elsewhere).

@gnzlbg
Copy link
Contributor

gnzlbg commented Sep 21, 2018

FWIW I gave it a quick shot too, and decided that it was tricky enough to deserve its own PR. I started with crossbeam, but while making the changes got the feeling that rayon might actually be enough.

@cuviper
Copy link

cuviper commented Oct 5, 2018

There is a thread-local structure (Resamples) which holds a borrow of the shared vector c. I couldn't figure out a way to do that in Rayon without creating a new Resamples instance for every iteration (potentially expensive because it contains a random number generator as well, which needs to be initialized from the thread_rng()) or crossing the borrow checker or both.

I'm hoping that rayon-rs/rayon#602 will help this kind of use case.

@bheisler
Copy link
Owner Author

bheisler commented Oct 6, 2018

That does look like it would help, yes.

huonw added a commit to huonw/criterion.rs that referenced this issue Jan 15, 2019
This does not seem to result in any regressions in performance, and
possibly some small (~5%) speed-ups. This is technically a breaking
change, as it required adding some trait bounds to some functions, but
anyone that is actually broken risks having memory
unsafety (non-`Send` types being sent across threads).

Work towards bheisler#195.
huonw added a commit to huonw/criterion.rs that referenced this issue Jan 15, 2019
This does not seem to result in any regressions in performance, and
possibly some small (~5%) speed-ups. This is technically a breaking
change, as it required adding some trait bounds to some functions, but
anyone that is actually broken risks having memory
unsafety (non-`Send` types being sent across threads).

Work towards bheisler#195.
@lemmih
Copy link
Collaborator

lemmih commented Jul 27, 2021

Status update. Criterion contains 10 unsafe blocks and 1 unsafe function:

  1. unsafe fn at_unchecked(&self, p: A) -> A {
  2. unsafe { self.at_unchecked(p) }
  3. unsafe { self.at_unchecked(A::cast(50)) }
  4. unsafe { mem::transmute(slice) }
  5. unsafe { mem::transmute(v) }
  6. unsafe { mem::transmute(slice) }
  7. unsafe { mem::transmute::<&[_], _>(v) }
  8. unsafe { Some((self.data.0.get_unchecked(i), self.data.1.get_unchecked(i))) }
  9. unsafe {
  • One of them is due to black_box and is pretty unavoidable.
  • Two of them are for transmuting between &[A] and &Sample<A>. This is UB since Sample<A> is not guaranteed to share the representation of [A]. We need to add #[repr(transparent)].
  • The rest are for unchecked slice indexing. Surely we can remove these without hurting performance too much. Some kind of benchmarking library would be helpful here.

@bheisler
Copy link
Owner Author

The black_box unsafe block may be fixable with a future version of rustc; the various teams have been (slowly) working towards standardizing a black box in the std::hint namespace. When that lands (which could be a while, mind) we can finally get rid of our fake black_box function entirely, or just make it delegate directly to the one in std.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Beginner Suitable for people new to Criterion.rs Enhancement
Projects
None yet
Development

No branches or pull requests

4 participants