- 
                Notifications
    You must be signed in to change notification settings 
- Fork 13.9k
Replace HashMap implementation with SwissTable #56241
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
          
     Closed
      
      
    
  
     Closed
                    Changes from all commits
      Commits
    
    
            Show all changes
          
          
            20 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      25f6e67
              
                WIP: Replace HashMap implementation with SwissTable
              
              
                Amanieu bd6164a
              
                Change Entry<'a, K, V, S> back to Entry<'a, K, V>
              
              
                Amanieu a818cf0
              
                Add missing #[may_dangle]
              
              
                Amanieu 2f89153
              
                Fix warning
              
              
                Amanieu 7fb0e90
              
                Fix bug in convert_special_to_empty_and_full_to_deleted
              
              
                Amanieu 7f493fa
              
                Fix convert_special_to_empty_and_full_to_deleted again
              
              
                Amanieu 8d1b330
              
                Fix an overflow in subtract
              
              
                Amanieu 2bec4c8
              
                Fix a test
              
              
                Amanieu e2e743a
              
                Inline ALL THE THINGS!
              
              
                Amanieu b1b2032
              
                Fix tests
              
              
                Amanieu b87e919
              
                Fix test_behavior_resize_policy
              
              
                Amanieu 9eb3eb8
              
                Apply feedback
              
              
                Amanieu 5f44f16
              
                Implement try_reserve
              
              
                Amanieu 1ed8a6a
              
                Remove test for adaptive early resize
              
              
                Amanieu a7b34ab
              
                Fix test_try_reserve
              
              
                Amanieu 44a9ce8
              
                Remove search_bucket from raw_entry
              
              
                Amanieu d687300
              
                Fix variance for IterMut
              
              
                Amanieu ed31ad7
              
                Apply feedback for Group/BitMask
              
              
                Amanieu 3bee3c0
              
                Sync iterator changes from hashbrown
              
              
                Amanieu ac86ef6
              
                Minor changes
              
              
                Amanieu File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
      
      Oops, something went wrong.
      
    
  
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| use super::imp::{BitMaskWord, BITMASK_MASK, BITMASK_STRIDE}; | ||
| use core::intrinsics; | ||
|  | ||
| /// A bit mask which contains the result of a `Match` operation on a `Group` and | ||
| /// allows iterating through them. | ||
| /// | ||
| /// The bit mask is arranged so that low-order bits represent lower memory | ||
| /// addresses for group match results. | ||
| /// | ||
| /// For implementation reasons, the bits in the set may be sparsely packed, so | ||
| /// that there is only one bit-per-byte used (the high bit, 7). If this is the | ||
| /// case, `BITMASK_STRIDE` will be 8 to indicate a divide-by-8 should be | ||
| /// performed on counts/indices to normalize this difference. `BITMASK_MASK` is | ||
| /// similarly a mask of all the actually-used bits. | ||
| #[derive(Copy, Clone)] | ||
| pub struct BitMask(pub BitMaskWord); | ||
|         
                  Amanieu marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| impl BitMask { | ||
| /// Returns a new `BitMask` with all bits inverted. | ||
| #[inline] | ||
| #[must_use] | ||
|         
                  Amanieu marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| pub fn invert(self) -> BitMask { | ||
| BitMask(self.0 ^ BITMASK_MASK) | ||
| } | ||
|  | ||
| /// Returns a new `BitMask` with the lowest bit removed. | ||
| #[inline] | ||
| #[must_use] | ||
| pub fn remove_lowest_bit(self) -> BitMask { | ||
| BitMask(self.0 & (self.0 - 1)) | ||
| } | ||
| /// Returns whether the `BitMask` has at least one set bit. | ||
| #[inline] | ||
| pub fn any_bit_set(self) -> bool { | ||
| self.0 != 0 | ||
| } | ||
|  | ||
| /// Returns the first set bit in the `BitMask`, if there is one. | ||
| #[inline] | ||
| pub fn lowest_set_bit(self) -> Option<usize> { | ||
| if self.0 == 0 { | ||
| None | ||
| } else { | ||
| Some(unsafe { self.lowest_set_bit_nonzero() }) | ||
|         
                  Amanieu marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| } | ||
| } | ||
|  | ||
| /// Returns the first set bit in the `BitMask`, if there is one. The | ||
| /// bitmask must not be empty. | ||
| #[inline] | ||
| pub unsafe fn lowest_set_bit_nonzero(self) -> usize { | ||
| intrinsics::cttz_nonzero(self.0) as usize / BITMASK_STRIDE | ||
| } | ||
|  | ||
| /// Returns the number of trailing zeroes in the `BitMask`. | ||
| #[inline] | ||
| pub fn trailing_zeros(self) -> usize { | ||
| // ARM doesn't have a trailing_zeroes instruction, and instead uses | ||
| // reverse_bits (RBIT) + leading_zeroes (CLZ). However older ARM | ||
| // versions (pre-ARMv7) don't have RBIT and need to emulate it | ||
| // instead. Since we only have 1 bit set in each byte on ARM, we can | ||
| // use swap_bytes (REV) + leading_zeroes instead. | ||
| if cfg!(target_arch = "arm") && BITMASK_STRIDE % 8 == 0 { | ||
| self.0.swap_bytes().leading_zeros() as usize / BITMASK_STRIDE | ||
| } else { | ||
| self.0.trailing_zeros() as usize / BITMASK_STRIDE | ||
| } | ||
| } | ||
|  | ||
| /// Returns the number of leading zeroes in the `BitMask`. | ||
| #[inline] | ||
| pub fn leading_zeros(self) -> usize { | ||
| self.0.leading_zeros() as usize / BITMASK_STRIDE | ||
| } | ||
| } | ||
|  | ||
| impl IntoIterator for BitMask { | ||
| type Item = usize; | ||
| type IntoIter = BitMaskIter; | ||
|  | ||
| #[inline] | ||
| fn into_iter(self) -> BitMaskIter { | ||
| BitMaskIter(self) | ||
| } | ||
| } | ||
|  | ||
| /// Iterator over the contents of a `BitMask`, returning the indicies of set | ||
| /// bits. | ||
| pub struct BitMaskIter(BitMask); | ||
|  | ||
| impl Iterator for BitMaskIter { | ||
| type Item = usize; | ||
|  | ||
| #[inline] | ||
| fn next(&mut self) -> Option<usize> { | ||
| let bit = self.0.lowest_set_bit()?; | ||
| self.0 = self.0.remove_lowest_bit(); | ||
| Some(bit) | ||
| } | ||
| } | ||
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| use super::bitmask::BitMask; | ||
| use super::EMPTY; | ||
| use core::{mem, ptr}; | ||
|  | ||
| // Use the native word size as the group size. Using a 64-bit group size on | ||
| // a 32-bit architecture will just end up being more expensive because | ||
| // shifts and multiplies will need to be emulated. | ||
| #[cfg(any( | ||
| target_pointer_width = "64", | ||
| target_arch = "aarch64", | ||
| target_arch = "x86_64", | ||
| ))] | ||
| type GroupWord = u64; | ||
| #[cfg(all( | ||
| target_pointer_width = "32", | ||
| not(target_arch = "aarch64"), | ||
| not(target_arch = "x86_64"), | ||
| ))] | ||
| type GroupWord = u32; | ||
|  | ||
| pub type BitMaskWord = GroupWord; | ||
| pub const BITMASK_STRIDE: usize = 8; | ||
| // We only care about the highest bit of each byte for the mask. | ||
| pub const BITMASK_MASK: BitMaskWord = 0x8080_8080_8080_8080u64 as GroupWord; | ||
|  | ||
| /// Helper function to replicate a byte across a `GroupWord`. | ||
| #[inline] | ||
| fn repeat(byte: u8) -> GroupWord { | ||
| let repeat = byte as GroupWord; | ||
| let repeat = repeat | repeat.wrapping_shl(8); | ||
| let repeat = repeat | repeat.wrapping_shl(16); | ||
| // This last line is a no-op with a 32-bit GroupWord | ||
| repeat | repeat.wrapping_shl(32) | ||
| } | ||
|  | ||
| /// Abstraction over a group of control bytes which can be scanned in | ||
| /// parallel. | ||
| /// | ||
| /// This implementation uses a word-sized integer. | ||
| #[derive(Copy, Clone)] | ||
| pub struct Group(GroupWord); | ||
|  | ||
| // We perform all operations in the native endianess, and convert to | ||
| // little-endian just before creating a BitMask. The can potentially | ||
| // enable the compiler to eliminate unnecessary byte swaps if we are | ||
| // only checking whether a BitMask is empty. | ||
|         
                  Amanieu marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| impl Group { | ||
| /// Number of bytes in the group. | ||
| pub const WIDTH: usize = mem::size_of::<Self>(); | ||
|  | ||
| /// Returns a full group of empty bytes, suitable for use as the initial | ||
| /// value for an empty hash table. | ||
| /// | ||
| /// This is guaranteed to be aligned to the group size. | ||
| #[inline] | ||
| pub fn static_empty() -> &'static [u8] { | ||
| union AlignedBytes { | ||
| _align: Group, | ||
| bytes: [u8; Group::WIDTH], | ||
| }; | ||
| const ALIGNED_BYTES: AlignedBytes = AlignedBytes { | ||
| bytes: [EMPTY; Group::WIDTH], | ||
| }; | ||
| unsafe { &ALIGNED_BYTES.bytes } | ||
| } | ||
|         
                  Amanieu marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| /// Loads a group of bytes starting at the given address. | ||
| #[inline] | ||
| pub unsafe fn load(ptr: *const u8) -> Group { | ||
| Group(ptr::read_unaligned(ptr as *const _)) | ||
| } | ||
|  | ||
| /// Loads a group of bytes starting at the given address, which must be | ||
| /// aligned to `mem::align_of::<Group>()`. | ||
| #[inline] | ||
| pub unsafe fn load_aligned(ptr: *const u8) -> Group { | ||
| debug_assert_eq!(ptr as usize & (mem::align_of::<Group>() - 1), 0); | ||
| Group(ptr::read(ptr as *const _)) | ||
|         
                  Amanieu marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| } | ||
|  | ||
| /// Stores the group of bytes to the given address, which must be | ||
| /// aligned to `mem::align_of::<Group>()`. | ||
| #[inline] | ||
| pub unsafe fn store_aligned(&self, ptr: *mut u8) { | ||
| debug_assert_eq!(ptr as usize & (mem::align_of::<Group>() - 1), 0); | ||
| ptr::write(ptr as *mut _, self.0); | ||
| } | ||
|  | ||
| /// Returns a `BitMask` indicating all bytes in the group which *may* | ||
| /// have the given value. | ||
| /// | ||
| /// This function may return a false positive in certain cases where | ||
| /// the byte in the group differs from the searched value only in its | ||
| /// lowest bit. This is fine because: | ||
| /// - This never happens for `EMPTY` and `DELETED`, only full entries. | ||
| /// - The check for key equality will catch these. | ||
| /// - This only happens if there is at least 1 true match. | ||
| /// - The chance of this happening is very low (< 1% chance per byte). | ||
| #[inline] | ||
| pub fn match_byte(&self, byte: u8) -> BitMask { | ||
| // This algorithm is derived from | ||
| // http://graphics.stanford.edu/~seander/bithacks.html##ValueInWord | ||
| let cmp = self.0 ^ repeat(byte); | ||
| BitMask((cmp.wrapping_sub(repeat(0x01)) & !cmp & repeat(0x80)).to_le()) | ||
| } | ||
|  | ||
| /// Returns a `BitMask` indicating all bytes in the group which are | ||
| /// `EMPTY`. | ||
| #[inline] | ||
| pub fn match_empty(&self) -> BitMask { | ||
|         
                  Amanieu marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| // If the high bit is set, then the byte must be either: | ||
| // 1111_1111 (EMPTY) or 1000_0000 (DELETED). | ||
| // So we can just check if the top two bits are 1 by ANDing them. | ||
| BitMask((self.0 & (self.0 << 1) & repeat(0x80)).to_le()) | ||
|         
                  Amanieu marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| } | ||
|  | ||
| /// Returns a `BitMask` indicating all bytes in the group which are | ||
| /// `EMPTY` or `DELETED`. | ||
| #[inline] | ||
| pub fn match_empty_or_deleted(&self) -> BitMask { | ||
|         
                  Amanieu marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| // A byte is EMPTY or DELETED iff the high bit is set | ||
| BitMask((self.0 & repeat(0x80)).to_le()) | ||
| } | ||
|  | ||
| /// Performs the following transformation on all bytes in the group: | ||
| /// - `EMPTY => EMPTY` | ||
| /// - `DELETED => EMPTY` | ||
| /// - `FULL => DELETED` | ||
| #[inline] | ||
| pub fn convert_special_to_empty_and_full_to_deleted(&self) -> Group { | ||
|         
                  Amanieu marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| // Map high_bit = 1 (EMPTY or DELETED) to 1111_1111 | ||
| // and high_bit = 0 (FULL) to 1000_0000 | ||
| // | ||
| // Here's this logic expanded to concrete values: | ||
| // let full = 1000_0000 (true) or 0000_0000 (false) | ||
| // !1000_0000 + 1 = 0111_1111 + 1 = 1000_0000 (no carry) | ||
| // !0000_0000 + 0 = 1111_1111 + 0 = 1111_1111 (no carry) | ||
| let full = !self.0 & repeat(0x80); | ||
| Group(!full + (full >> 7)) | ||
| } | ||
| } | ||
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.