-
Notifications
You must be signed in to change notification settings - Fork 140
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
feat: optimize no-op bitfield operations #849
Conversation
0f016c6
to
5df7645
Compare
Codecov Report
@@ Coverage Diff @@
## master #849 +/- ##
==========================================
- Coverage 51.29% 51.02% -0.27%
==========================================
Files 123 124 +1
Lines 10060 10149 +89
==========================================
+ Hits 5160 5179 +19
- Misses 4900 4970 +70
|
ipld/bitfield/src/lib.rs
Outdated
} | ||
} | ||
|
||
impl BitOrAssign<&BitField> for BitField { | ||
#[inline] | ||
fn bitor_assign(&mut self, rhs: &BitField) { | ||
*self = &*self | rhs; | ||
if !rhs.is_trivially_empty() { |
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.
saves a clone
ipld/bitfield/src/lib.rs
Outdated
#[inline] | ||
fn bitor_assign(&mut self, rhs: BitField) { | ||
// by-value to optimize the case where one is empty. | ||
let s = std::mem::take(self); |
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 may save a clone
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 don't quite follow. The |
op on 430 uses the bitor
's defined above. Are you saying impl BitOr<&BitField> for &BitField {
is less efficient than impl BitOr<BitField> for &BitField {
apart from the clone? All I can see different is the clone.
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.
That's the difference. But I'll see if I can simplify a bit.
ipld/bitfield/src/lib.rs
Outdated
@@ -367,7 +406,20 @@ impl BitAnd<&BitField> for &BitField { | |||
|
|||
#[inline] | |||
fn bitand(self, rhs: &BitField) -> Self::Output { | |||
BitField::from_ranges(self.ranges().intersection(rhs.ranges())) | |||
if self.is_trivially_empty() || rhs.is_trivially_empty() { | |||
BitField::new() |
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 free.
ipld/bitfield/src/lib.rs
Outdated
} | ||
} | ||
|
||
impl BitAnd<BitField> for BitField { |
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.
Implementing by-value ops encourages users to perform by-value ops where possible (possibly leading to optimizations).
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 uses impl BitAnd<&BitField> for &BitField {
under the hood so there's no speedup to using this instead correct? But you're saying just letting them work with values directly can lead to user side optimizations?
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.
Ah, no. I just don't want the user to try a by-value operation, notice that it doesn't work, then assume that bitfields don't support by-value operations.
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.
Which is what I’ve been doing recently. 😂
We have quite a few bitfield operations where one bitfield is empty. Optimize them.
5df7645
to
d329f5d
Compare
(are we supposed to backport this to |
Ah.... no. This create isn't actually a part of the FVM, we just stashed it in this repo because we're LAAAAAAAAAAAAAAZY. We can safely release this from master. |
We've discussed breaking all the "ipld" creates into a new repo, but haven't because that's more work. |
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 so strong on rust's memory model so some of the tricks used here go over my head. However it looks correct.
@@ -297,6 +297,12 @@ impl BitField { | |||
.all(|bit| self.unset.contains(&bit)) | |||
} | |||
|
|||
/// Returns `true` if the bit field is _trivially_ empty. This is significantly faster than | |||
/// checking if it's actually empty, but can be used to short-circuite certain operations. |
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.
Making sure I understand: to check if its actually empty we'd need to apply unset bits across set bits and ranges?
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. Basically, if it returns true, it's definitely empty. But if it returns false, we won't be able to know without doing a bit of work.
ipld/bitfield/src/lib.rs
Outdated
#[inline] | ||
fn bitor_assign(&mut self, rhs: BitField) { | ||
// by-value to optimize the case where one is empty. | ||
let s = std::mem::take(self); |
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 don't quite follow. The |
op on 430 uses the bitor
's defined above. Are you saying impl BitOr<&BitField> for &BitField {
is less efficient than impl BitOr<BitField> for &BitField {
apart from the clone? All I can see different is the clone.
ipld/bitfield/src/lib.rs
Outdated
|
||
#[inline] | ||
fn bitor(self, rhs: &BitField) -> Self::Output { | ||
if rhs.is_trivially_empty() { |
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.
nit: unless I'm missing some speedup gain it would be nice to check in a consistent order. Other bitors do self, then rhs
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 trying to avoid a clone if both are trivially empty. It probably won't matter in most case so I'll switch it back.
ipld/bitfield/src/lib.rs
Outdated
} | ||
} | ||
|
||
impl BitAnd<BitField> for BitField { |
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 uses impl BitAnd<&BitField> for &BitField {
under the hood so there's no speedup to using this instead correct? But you're saying just letting them work with values directly can lead to user side optimizations?
And put them in a new file. This is verbose, but should be easy to understand and reason about.
@ZenGround0 I broke the ops into a separate file and implemented them on all ref/non-ref combinations. It's very verbose, but should be easier to reason about. |
ipld/bitfield/src/ops.rs
Outdated
#[inline] | ||
fn bitxor(self, rhs: &BitField) -> Self::Output { | ||
// Nothing to optimize. | ||
BitField::from_ranges(self.ranges().symmetric_difference(rhs.ranges())) |
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.
Wouldn't XOR be a no-op if one of the sets is trivially empty?
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.
Other than the XOR question, LGTM.
5674d74
to
62d53e0
Compare
62d53e0
to
f642707
Compare
Signed-off-by: Jakub Sztandera <[email protected]>
We have quite a few bitfield operations where one bitfield is empty.
Optimize them.