-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
[WIP] Use a custom hash set for interning #50959
Conversation
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
☔ The latest upstream changes (presumably #50520) made this pull request unmergeable. Please resolve the merge conflicts. |
@Zoxc, how much faster do you expect this to be? |
Ping from triage, @Zoxc ! You have a review question and merge conflicts. |
@bors try |
[WIP] Use a custom hash set for interning r? @michaelwoerister
☀️ Test successful - status-travis |
@Mark-Simulacrum Can I get a perf run here? |
Perf run queued. |
Looks very promising! r? @eddyb |
I am not qualified to review this. r? @gankro @bluss @nikomatsakis |
|
Didn't @gankro propose an API where you could do this, although misusing it would put the hashmap in an unusual state (not unsafe, just potentially misbehaving)? |
Ping from triage @gankro! This PR needs your review. |
Ping from triage! This PR needs a review, can @gankro or someone else from @rust-lang/compiler review this? |
Ping from triage @gankro, @rust-lang/compiler, will someone have time to review this PR? |
Discussed in the @rust-lang/compiler meeting. Everybody I think generally agreed that all things being equal using libstd is better, but it's also ok to land this in the meantime if it's a solid win (as it appears to be). |
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've done a first pass over this and left some notes. I'll do another pass tomorrow. There are some things that I haven't looked at at all yet.
} | ||
} | ||
|
||
pub fn make_hash<T: ?Sized, S>(hash_state: &S, t: &T) -> u64 |
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 could directly return a u32
const ENTRIES_PER_GROUP: usize = 5; | ||
|
||
#[repr(align(64), C)] | ||
pub struct Group { |
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 think "Bucket" is the more canonical term.
|
||
#[inline(always)] | ||
fn set(&mut self, pos: usize, hash: u32, value: u64) { | ||
unsafe { |
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.
Could you add a debug_assert!(pos < ENTRIES_PER_GROUP)
here, please?
#[inline(always)] | ||
fn search_with<K, F: FnMut(&K) -> bool>(&self, eq: &mut F, hash: u32) -> Option<(usize, bool)> { | ||
for i in 0..ENTRIES_PER_GROUP { | ||
let h = unsafe { *self.hashes.get_unchecked(i) }; |
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 think the compiler is smart enough to omit the bounds check by itself in cases like this.
} | ||
|
||
Table { | ||
group_mask: group_count.wrapping_sub(1), |
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 wrapping around here always signify a bug?
//println!("capacity1 {} capacity {}", capacity1, capacity); | ||
assert!(capacity < capacity2); | ||
|
||
for i in 0..group_count { |
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.
If you use Global.alloc_zeroed()
above, you don't have to clear things manually here. It might also be faster.
|
||
#[inline(always)] | ||
fn iter<F: FnMut(u32, u64)>(&self, f: &mut F) { | ||
for i in 0..ENTRIES_PER_GROUP { |
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.
If you do for i in 0 .. self.size
here you can avoid always setting the most significant bit it the hash.
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.
LLVM won't unroll the loop in that case
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.
Right, but you'll have fewer iterations and you can omit the h != 0
check. It might be a win overall.
pub struct Group { | ||
hashes: [u32; ENTRIES_PER_GROUP], | ||
size: u32, | ||
values: [u64; ENTRIES_PER_GROUP], |
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.
Have you tried this on a 32-bit system? Is there a reason not to make this generic? As in:
struct Group<T: Sized> {
hashes: [u32; ENTRIES_PER_GROUP],
size: u32,
values: [*const T; ENTRIES_PER_GROUP],
}
group_mask: usize, | ||
size: usize, | ||
capacity: usize, | ||
groups: Unique<Group>, |
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.
Why not just use a Vec
or Box<[]>
for this?
//let capacity = (capacity1 * 10 + 10 - 1) / 11; | ||
let capacity = (capacity1 * 10 + 10 - 1) / 13; | ||
//println!("capacity1 {} capacity {}", capacity1, capacity); | ||
assert!(capacity < capacity2); |
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 understand this capacity calculation. It seems to be adapted from what the std hashmap does but it could need better variable names and comments explaining the reasoning behind it.
|
||
const ENTRIES_PER_GROUP: usize = 5; | ||
|
||
#[repr(align(64), C)] |
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.
Why the #[repr(..)]
?
OK, I think I have listed all my concerns. Thanks for the PR, @Zoxc! I like the general approach and that hash map implementation is simple. I think it could do with less unsafe code though. |
Ping from triage, @Zoxc: Some changes have been requested to this PR. |
Thank you for this PR @Zoxc! Unfortunately we haven't heard from you on this in a while, so I'm closing the PR to keep things tidy. Don't worry though, if you'll have time again in the future please reopen this PR, we'll be happy to review it again! |
r? @michaelwoerister