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

Use non-zero optimization for Generation #447

Merged
merged 15 commits into from
Sep 21, 2018
Merged

Use non-zero optimization for Generation #447

merged 15 commits into from
Sep 21, 2018

Conversation

andrewhickman
Copy link
Contributor

@andrewhickman andrewhickman commented Aug 4, 2018

As promised in issue #436, this PR uses NonZeroU32 in Generation. Note this bumps the minimum supported rust version to 1.28 - is this a concern? If so I could put this under a feature flag.


This change is Reviewable

@torkleyy
Copy link
Member

torkleyy commented Aug 4, 2018

Hi! Unfortunately, this is a nightly-only feature, we cannot use this optimization.

@andrewhickman
Copy link
Contributor Author

NonZeroU32 was stabilized a couple days ago with rust 1.28.0.

Copy link
Member

@torkleyy torkleyy left a comment

Choose a reason for hiding this comment

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

Ah, now you removed the feature requirement. I think this makes sense, but I don't want it to be merged yet because 1.28 is the latest version. Can we come back to this in a few weeks?

@Aceeri
Copy link
Member

Aceeri commented Sep 14, 2018

@torkleyy @andrewhickman 1.29 is released now, might be worth looking at rebasing/merging this (unless we want a larger buffer size).

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 20, 2018

Should probably be released in 0.13, I'm doing a 0.12.3 right now

@@ -383,20 +406,38 @@ impl Generation {
/// Panics in debug mode if it's not alive.
fn die(&mut self) {
debug_assert!(self.is_alive());
self.0 = -self.0;
self.0 = NonZeroU32::new(-self.id() as u32).map(Generation);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't this underflow and therefore panic in debug builds?

Copy link
Member

Choose a reason for hiding this comment

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

Sure can, so yeah that should be corrected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Which operation can overflow?

Copy link
Contributor

Choose a reason for hiding this comment

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

@andrewhickman The negation.

Copy link
Contributor

Choose a reason for hiding this comment

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

@andrewhickman Or alternately, the cast. I'm not sure where the problem would actually occur.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The assert should ensure that id() is positive and the only overflow case is where id() is i32::MIN so I think this one is correct. AFAIK signed-ness casts are always no-ops in rust so that should be OK as well

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok; I can accept the assert! making the negation safe, but I'd like to see where casts from signed to unsigned are supposed to be defined as bitcasts.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member

@Xaeroxe Xaeroxe Sep 20, 2018

Choose a reason for hiding this comment

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

id() can only ever be a positive number by virtue of the fact that it is built from a NonZeroU32. Won't this always panic?

scratch that, I was missing context

Copy link
Member

Choose a reason for hiding this comment

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

Hm ok but it can only ever be positive or 0. I think we still have a problem.

/// Panics if it is alive.
fn raised(&self) -> Generation {
assert!(!self.is_alive());
Generation(unsafe { NonZeroU32::new_unchecked((1 - self.id()) as u32) })
Copy link
Contributor

Choose a reason for hiding this comment

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

Another very similar possible underflow here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that wrapping will give the wrong behaviour here, sending id from i32::MIN + 1 to i32::MIN and vice versa. I think it would be better to just unconditionally panic here. Potentially we could start wrapping the generations back around to 1 but that sounds risky to me.

@andrewhickman
Copy link
Contributor Author

andrewhickman commented Sep 20, 2018

The test failure seems to be caused by ab43ce1

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 20, 2018

I removed is_alive() and raised() from the generation because if a Generation can no longer be less than 0 then it's not possible for that structure to register as "dead". Since a structure can never be dead what's the point of "check if this is alive and if so return it". Perhaps I missed something but is that not what a NonZeroU32 does?

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 20, 2018

Alright, after having read all the overflow/underflow bits and pieced this together I understand now why what I did broke the PR. I'm not super sure though that the loss in readability is worth the minute performance gains to be had from this.

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 20, 2018

@andrewhickman would you rather I put the PR back together or would you prefer to?

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 20, 2018

I don't know, I'm not even sure it's worth the effort to be honest. Specs is hard enough to read as is, and while this is technologically an improvement relying on overflow behavior to implement negatives makes it so much harder to maintain that I question the value of this.

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 20, 2018

Ok, so I've been thinking about this a bit more and I actually see a way we can make this work. You've already made sure that the only entry and exit points from the Generation structure output to i32 correctly, the next thing I feel this really needs to be perfect is that everywhere we're casting to a NonZeroU32 or to a i32 we should provide a comment explaining the overflow/underflow behavior. This way we still have a Generation value with a nice interface, and specs maintainers understand what's going on.

@andrewhickman
Copy link
Contributor Author

@Xaeroxe not sure from your comments if you worked this out but the only reason I used NonZeroU32 is that NonZeroI32 was never stabilised. So the inner value can be negative, but the bits are stored as u32. I'll add a comment explaining this and fix this PR up tomorrow.

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 20, 2018

Yeah I learned that from the original issue... probably should have read that first :P Sorry for all the trouble, and thanks for fixing my mistakes

@pthariensflame
Copy link
Contributor

pthariensflame commented Sep 20, 2018

If we wanted to go a little overboard, we could implement a NonZeroI32 on stable as a #[repr(transparent)] wrapper around NonZeroU32, delegating methods and trait implementations wherever possible.

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 20, 2018

Maybe we ought to make a crate ;)

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 21, 2018

@pthariensflame K you're gonna laugh but...

I did it! https://crates.io/crates/nonzero_signed

@andrewhickman
Copy link
Contributor Author

@Xaeroxe ooh nice! Shall we use it here?

@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 21, 2018 via email

@Xaeroxe Xaeroxe merged commit 5c445f3 into amethyst:master Sep 21, 2018
@Xaeroxe
Copy link
Member

Xaeroxe commented Sep 21, 2018

Thank you!

@andrewhickman andrewhickman deleted the non-zero-gen branch September 21, 2018 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants