From 9667140308900093ac84069016ee2699ba8c1e52 Mon Sep 17 00:00:00 2001 From: arkpar Date: Fri, 6 Oct 2023 10:51:52 +0200 Subject: [PATCH 1/5] Force alignment on all chunk buffers --- src/db.rs | 2 +- src/file.rs | 4 +-- src/index.rs | 88 ++++++++++++++++++++-------------------------------- src/lib.rs | 3 ++ src/log.rs | 2 +- 5 files changed, 40 insertions(+), 59 deletions(-) diff --git a/src/db.rs b/src/db.rs index ed80d553..c87b144c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -87,7 +87,7 @@ impl Borrow<[u8]> for RcValue { impl Borrow> for RcValue { fn borrow(&self) -> &Vec { - self.value().borrow() + self.value() } } diff --git a/src/file.rs b/src/file.rs index d6bc0f49..9136a2c9 100644 --- a/src/file.rs +++ b/src/file.rs @@ -50,7 +50,7 @@ pub fn madvise_random(_map: &mut memmap2::MmapMut) {} fn mmap(file: &std::fs::File, len: usize) -> Result { #[cfg(not(test))] const RESERVE_ADDRESS_SPACE: usize = 1024 * 1024 * 1024; // 1 Gb - // Use different value for tests to work around docker limits on the test machine. + // Use a different value for tests to work around docker limits on the test machine. #[cfg(test)] const RESERVE_ADDRESS_SPACE: usize = 64 * 1024 * 1024; // 64 Mb @@ -92,7 +92,7 @@ impl TableFile { } else { capacity = len / entry_size as u64; } - let mut map = mmap(&file, len as usize)?; + let map = mmap(&file, len as usize)?; Some((map, file)) } else { None diff --git a/src/index.rs b/src/index.rs index a022f1a2..1f9568bd 100644 --- a/src/index.rs +++ b/src/index.rs @@ -27,9 +27,12 @@ const META_SIZE: usize = 16 * 1024; // Contains header and column stats const ENTRY_BITS: u8 = 64; pub const ENTRY_BYTES: usize = ENTRY_BITS as usize / 8; -const EMPTY_CHUNK: Chunk = [0u8; CHUNK_LEN]; +const EMPTY_CHUNK: Chunk = Chunk([0u8; CHUNK_LEN]); +const EMPTY_ENTRIES: [Entry; CHUNK_ENTRIES] = [Entry::empty(); CHUNK_ENTRIES]; -pub type Chunk = [u8; CHUNK_LEN]; +#[repr(C, align(8))] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct Chunk(pub [u8; CHUNK_LEN]); #[allow(clippy::assertions_on_constants)] const _: () = assert!(META_SIZE >= HEADER_SIZE + stats::TOTAL_SIZE); @@ -78,7 +81,7 @@ impl Entry { self.0 } - fn empty() -> Self { + const fn empty() -> Self { Entry(0) } @@ -246,9 +249,9 @@ impl IndexTable { Ok(()) } - fn chunk_at(index: u64, map: &memmap2::MmapMut) -> Result<&[u8; CHUNK_LEN]> { + fn chunk_at(index: u64, map: &memmap2::MmapMut) -> Result<&Chunk> { let offset = META_SIZE + index as usize * CHUNK_LEN; - let ptr = unsafe { &*(map[offset..offset + CHUNK_LEN].as_ptr() as *const [u8; CHUNK_LEN]) }; + let ptr = unsafe { &*(map[offset..offset + CHUNK_LEN].as_ptr() as *const Chunk) }; Ok(try_io!(Ok(ptr))) } @@ -260,32 +263,18 @@ impl IndexTable { Ok(try_io!(Ok(ptr))) } #[cfg(target_arch = "x86_64")] - fn find_entry( - &self, - key_prefix: u64, - sub_index: usize, - chunk: &[u8; CHUNK_LEN], - ) -> (Entry, usize) { + fn find_entry(&self, key_prefix: u64, sub_index: usize, chunk: &Chunk) -> (Entry, usize) { self.find_entry_sse2(key_prefix, sub_index, chunk) } #[cfg(not(target_arch = "x86_64"))] - fn find_entry( - &self, - key_prefix: u64, - sub_index: usize, - chunk: &[u8; CHUNK_LEN], - ) -> (Entry, usize) { + fn find_entry(&self, key_prefix: u64, sub_index: usize, chunk: &Chunk) -> (Entry, usize) { self.find_entry_base(key_prefix, sub_index, chunk) } #[cfg(target_arch = "x86_64")] - fn find_entry_sse2( - &self, - key_prefix: u64, - sub_index: usize, - chunk: &[u8; CHUNK_LEN], - ) -> (Entry, usize) { + fn find_entry_sse2(&self, key_prefix: u64, sub_index: usize, chunk: &Chunk) -> (Entry, usize) { + let chunk = &chunk.0; assert!(chunk.len() >= CHUNK_ENTRIES * 8); // Bound checking (not done by SIMD instructions) const _: () = assert!( CHUNK_ENTRIES % 4 == 0, @@ -329,12 +318,7 @@ impl IndexTable { (Entry::empty(), 0) } - fn find_entry_base( - &self, - key_prefix: u64, - sub_index: usize, - chunk: &[u8; CHUNK_LEN], - ) -> (Entry, usize) { + fn find_entry_base(&self, key_prefix: u64, sub_index: usize, chunk: &Chunk) -> (Entry, usize) { let partial_key = Entry::extract_key(key_prefix, self.id.index_bits()); for i in sub_index..CHUNK_ENTRIES { let entry = Self::read_entry(chunk, i); @@ -378,18 +362,16 @@ impl IndexTable { } pub fn entries(&self, chunk_index: u64, log: &impl LogQuery) -> Result<[Entry; CHUNK_ENTRIES]> { - let mut chunk = [0; CHUNK_LEN]; if let Some(entry) = log.with_index(self.id, chunk_index, |chunk| Self::transmute_chunk(*chunk)) { return Ok(entry) } if let Some(map) = &*self.map.read() { - let source = Self::chunk_at(chunk_index, map)?; - chunk.copy_from_slice(source); + let chunk = *Self::chunk_at(chunk_index, map)?; return Ok(Self::transmute_chunk(chunk)) } - Ok(Self::transmute_chunk(EMPTY_CHUNK)) + Ok(EMPTY_ENTRIES) } pub fn sorted_entries(&self) -> Result> { @@ -415,7 +397,7 @@ impl IndexTable { } #[inline(always)] - fn transmute_chunk(chunk: [u8; CHUNK_LEN]) -> [Entry; CHUNK_ENTRIES] { + fn transmute_chunk(chunk: Chunk) -> [Entry; CHUNK_ENTRIES] { let mut result: [Entry; CHUNK_ENTRIES] = unsafe { std::mem::transmute(chunk) }; if !cfg!(target_endian = "little") { for item in result.iter_mut() { @@ -426,13 +408,13 @@ impl IndexTable { } #[inline(always)] - fn write_entry(entry: &Entry, at: usize, chunk: &mut [u8; CHUNK_LEN]) { - chunk[at * 8..at * 8 + 8].copy_from_slice(&entry.as_u64().to_le_bytes()); + fn write_entry(entry: &Entry, at: usize, chunk: &mut Chunk) { + chunk.0[at * 8..at * 8 + 8].copy_from_slice(&entry.as_u64().to_le_bytes()); } #[inline(always)] - fn read_entry(chunk: &[u8; CHUNK_LEN], at: usize) -> Entry { - Entry::from_u64(u64::from_le_bytes(chunk[at * 8..at * 8 + 8].try_into().unwrap())) + fn read_entry(chunk: &Chunk, at: usize) -> Entry { + Entry::from_u64(u64::from_le_bytes(chunk.0[at * 8..at * 8 + 8].try_into().unwrap())) } #[inline(always)] @@ -444,7 +426,7 @@ impl IndexTable { &self, key_prefix: u64, address: Address, - source: &[u8], + mut chunk: Chunk, sub_index: Option, log: &mut LogWriter, ) -> Result { @@ -454,8 +436,6 @@ impl IndexTable { log::warn!(target: "parity-db", "{}: Address space overflow at {}: {}", self.id, chunk_index, address); return Ok(PlanOutcome::NeedReindex) } - let mut chunk = [0; CHUNK_LEN]; - chunk.copy_from_slice(source); let partial_key = Entry::extract_key(key_prefix, self.id.index_bits()); let new_entry = Entry::new(address, partial_key, self.id.index_bits()); if let Some(i) = sub_index { @@ -494,27 +474,25 @@ impl IndexTable { let chunk_index = self.chunk_index(key_prefix); if let Some(chunk) = log.with_index(self.id, chunk_index, |chunk| *chunk) { - return self.plan_insert_chunk(key_prefix, address, &chunk, sub_index, log) + return self.plan_insert_chunk(key_prefix, address, chunk, sub_index, log) } if let Some(map) = &*self.map.read() { - let chunk = Self::chunk_at(chunk_index, map)?; + let chunk = *Self::chunk_at(chunk_index, map)?; return self.plan_insert_chunk(key_prefix, address, chunk, sub_index, log) } - let chunk = &EMPTY_CHUNK; + let chunk = EMPTY_CHUNK; self.plan_insert_chunk(key_prefix, address, chunk, sub_index, log) } fn plan_remove_chunk( &self, key_prefix: u64, - source: &[u8], + mut chunk: Chunk, sub_index: usize, log: &mut LogWriter, ) -> Result { - let mut chunk = [0; CHUNK_LEN]; - chunk.copy_from_slice(source); let chunk_index = self.chunk_index(key_prefix); let partial_key = Entry::extract_key(key_prefix, self.id.index_bits()); @@ -542,11 +520,11 @@ impl IndexTable { let chunk_index = self.chunk_index(key_prefix); if let Some(chunk) = log.with_index(self.id, chunk_index, |chunk| *chunk) { - return self.plan_remove_chunk(key_prefix, &chunk, sub_index, log) + return self.plan_remove_chunk(key_prefix, chunk, sub_index, log) } if let Some(map) = &*self.map.read() { - let chunk = Self::chunk_at(chunk_index, map)?; + let chunk = *Self::chunk_at(chunk_index, map)?; return self.plan_remove_chunk(key_prefix, chunk, sub_index, log) } @@ -679,9 +657,9 @@ mod test { let data_address = Address::from_u64((1 << index_bits) - 1); - let mut chunk = [0; CHUNK_ENTRIES * 8]; + let mut chunk = Chunk([0; CHUNK_ENTRIES * 8]); for (i, partial_key) in partial_keys.iter().enumerate() { - chunk[i * 8..(i + 1) * 8].copy_from_slice( + chunk.0[i * 8..(i + 1) * 8].copy_from_slice( &Entry::new(data_address, *partial_key, index_bits).as_u64().to_le_bytes(), ); } @@ -705,7 +683,7 @@ mod test { fn test_find_any_entry() { let table = IndexTable { id: TableId(18), map: RwLock::new(None), path: Default::default() }; - let mut chunk = [0u8; CHUNK_LEN]; + let mut chunk = Chunk([0u8; CHUNK_LEN]); let mut entries = [Entry::empty(); CHUNK_ENTRIES]; let mut keys = [0u64; CHUNK_ENTRIES]; let mut rng = rand::prelude::SmallRng::from_seed(Default::default()); @@ -742,7 +720,7 @@ mod test { fn test_find_entry_same_value() { let table = IndexTable { id: TableId(18), map: RwLock::new(None), path: Default::default() }; - let mut chunk = [0u8; CHUNK_LEN]; + let mut chunk = Chunk([0u8; CHUNK_LEN]); let key = 0x4242424242424242; let partial_key = Entry::extract_key(key, 18); let entry = Entry::new(Address::new(0, 0), partial_key, 18); @@ -765,7 +743,7 @@ mod test { fn test_find_entry_zero_pk() { let table = IndexTable { id: TableId(16), map: RwLock::new(None), path: Default::default() }; - let mut chunk = [0u8; CHUNK_LEN]; + let mut chunk = Chunk([0u8; CHUNK_LEN]); let zero_key = 0x0000000000000000; let entry = Entry::new(Address::new(1, 1), zero_key, 16); @@ -790,7 +768,7 @@ mod test { ) { let table = IndexTable { id: TableId(18), map: RwLock::new(None), path: Default::default() }; - let mut chunk = [0u8; CHUNK_LEN]; + let mut chunk = Chunk([0u8; CHUNK_LEN]); let mut keys = [0u64; CHUNK_ENTRIES]; let mut rng = rand::prelude::SmallRng::from_seed(Default::default()); for i in 0..CHUNK_ENTRIES { diff --git a/src/lib.rs b/src/lib.rs index ca2c155d..2cb432fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,3 +35,6 @@ pub type Key = [u8; KEY_SIZE]; #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] compile_error!("parity-db only supports x86_64 and aarch64"); + +#[cfg(not(target_endian = "little"))] +compile_error!("parity-db only supports little-endian platforms"); diff --git a/src/log.rs b/src/log.rs index 882b7ea3..2793170e 100644 --- a/src/log.rs +++ b/src/log.rs @@ -357,7 +357,7 @@ impl LogChange { while mask != 0 { let i = mask.trailing_zeros(); mask &= !(1 << i); - write(&chunk[i as usize * ENTRY_BYTES..(i as usize + 1) * ENTRY_BYTES])?; + write(&chunk.0[i as usize * ENTRY_BYTES..(i as usize + 1) * ENTRY_BYTES])?; } } } From 7c46415ecf1bb1413b0042fc2e720d38b2cb7690 Mon Sep 17 00:00:00 2001 From: arkpar Date: Fri, 6 Oct 2023 11:00:49 +0200 Subject: [PATCH 2/5] Update deps --- Cargo.toml | 2 +- admin/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4e7f92b7..66f38dba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ loom = { version = "0.5.1", optional = true } siphasher = "0.3.10" [dev-dependencies] -env_logger = "0.10.0" +env_logger = { version = "0.10.0", default-features = false, features = ["auto-color", "humantime"] } fdlimit = "0.2.1" rand = { version = "0.8.2", features = ["small_rng"] } tempfile = "3.2" diff --git a/admin/Cargo.toml b/admin/Cargo.toml index cae2c8b8..8978e098 100644 --- a/admin/Cargo.toml +++ b/admin/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] clap = { version = "4", features = ["derive"] } -env_logger = "0.10.0" +env_logger = { version = "0.10.0", default-features = false, features = ["auto-color", "humantime"] } fdlimit = "0.2.1" log = "0.4.8" parity-db = { path = ".." } From 90b9a6b287edd50405808b53a49f91c43ad47fcb Mon Sep 17 00:00:00 2001 From: arkpar Date: Fri, 6 Oct 2023 11:09:05 +0200 Subject: [PATCH 3/5] x64 build --- src/index.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.rs b/src/index.rs index 1f9568bd..39c43e2d 100644 --- a/src/index.rs +++ b/src/index.rs @@ -274,8 +274,7 @@ impl IndexTable { #[cfg(target_arch = "x86_64")] fn find_entry_sse2(&self, key_prefix: u64, sub_index: usize, chunk: &Chunk) -> (Entry, usize) { - let chunk = &chunk.0; - assert!(chunk.len() >= CHUNK_ENTRIES * 8); // Bound checking (not done by SIMD instructions) + assert!(chunk.0.len() >= CHUNK_ENTRIES * 8); // Bound checking (not done by SIMD instructions) const _: () = assert!( CHUNK_ENTRIES % 4 == 0, "We assume here we got buffer with a number of elements that is a multiple of 4" @@ -288,6 +287,7 @@ impl IndexTable { return self.find_entry_base(key_prefix, sub_index, chunk) } unsafe { + let chunk = &chunk.0; let target = _mm_set1_epi32(pk as i32); let shift_mask = _mm_set_epi64x(0, shift.into()); let mut i = (sub_index >> 2) << 2; // We keep an alignment of 4 From 4d341ae889bb1826191abfdb38c30b7c0e221dc8 Mon Sep 17 00:00:00 2001 From: arkpar Date: Fri, 6 Oct 2023 11:11:04 +0200 Subject: [PATCH 4/5] x64 build --- src/index.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/index.rs b/src/index.rs index 39c43e2d..7a0dbf6a 100644 --- a/src/index.rs +++ b/src/index.rs @@ -287,7 +287,6 @@ impl IndexTable { return self.find_entry_base(key_prefix, sub_index, chunk) } unsafe { - let chunk = &chunk.0; let target = _mm_set1_epi32(pk as i32); let shift_mask = _mm_set_epi64x(0, shift.into()); let mut i = (sub_index >> 2) << 2; // We keep an alignment of 4 @@ -297,11 +296,11 @@ impl IndexTable { // Then we remove the address by shifting such that the partial key is in the low // part let first_two = _mm_shuffle_epi32::<0b11011000>(_mm_srl_epi64( - _mm_loadu_si128(chunk[i * 8..].as_ptr() as *const __m128i), + _mm_loadu_si128(chunk.0[i * 8..].as_ptr() as *const __m128i), shift_mask, )); let last_two = _mm_shuffle_epi32::<0b11011000>(_mm_srl_epi64( - _mm_loadu_si128(chunk[(i + 2) * 8..].as_ptr() as *const __m128i), + _mm_loadu_si128(chunk.0[(i + 2) * 8..].as_ptr() as *const __m128i), shift_mask, )); // We set into current the input low parts From 24f17a2a79b5836a381668e618218dd1e2fe293a Mon Sep 17 00:00:00 2001 From: arkpar Date: Fri, 6 Oct 2023 11:52:29 +0200 Subject: [PATCH 5/5] Explicity chunk cloning --- src/index.rs | 38 ++++++++++++++++---------------------- src/log.rs | 6 +++--- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/index.rs b/src/index.rs index 7a0dbf6a..40c0721e 100644 --- a/src/index.rs +++ b/src/index.rs @@ -31,7 +31,7 @@ const EMPTY_CHUNK: Chunk = Chunk([0u8; CHUNK_LEN]); const EMPTY_ENTRIES: [Entry; CHUNK_ENTRIES] = [Entry::empty(); CHUNK_ENTRIES]; #[repr(C, align(8))] -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] pub struct Chunk(pub [u8; CHUNK_LEN]); #[allow(clippy::assertions_on_constants)] @@ -362,13 +362,13 @@ impl IndexTable { pub fn entries(&self, chunk_index: u64, log: &impl LogQuery) -> Result<[Entry; CHUNK_ENTRIES]> { if let Some(entry) = - log.with_index(self.id, chunk_index, |chunk| Self::transmute_chunk(*chunk)) + log.with_index(self.id, chunk_index, |chunk| *Self::transmute_chunk(chunk)) { return Ok(entry) } if let Some(map) = &*self.map.read() { - let chunk = *Self::chunk_at(chunk_index, map)?; - return Ok(Self::transmute_chunk(chunk)) + let chunk = Self::chunk_at(chunk_index, map)?; + return Ok(*Self::transmute_chunk(chunk)) } Ok(EMPTY_ENTRIES) } @@ -396,14 +396,8 @@ impl IndexTable { } #[inline(always)] - fn transmute_chunk(chunk: Chunk) -> [Entry; CHUNK_ENTRIES] { - let mut result: [Entry; CHUNK_ENTRIES] = unsafe { std::mem::transmute(chunk) }; - if !cfg!(target_endian = "little") { - for item in result.iter_mut() { - *item = Entry::from_u64(u64::from_le(item.0)); - } - } - result + fn transmute_chunk(chunk: &Chunk) -> &[Entry; CHUNK_ENTRIES] { + unsafe { std::mem::transmute(chunk) } } #[inline(always)] @@ -445,7 +439,7 @@ impl IndexTable { ); Self::write_entry(&new_entry, i, &mut chunk); log::trace!(target: "parity-db", "{}: Replaced at {}.{}: {}", self.id, chunk_index, i, new_entry.address(self.id.index_bits())); - log.insert_index(self.id, chunk_index, i as u8, &chunk); + log.insert_index(self.id, chunk_index, i as u8, chunk); return Ok(PlanOutcome::Written) } for i in 0..CHUNK_ENTRIES { @@ -453,7 +447,7 @@ impl IndexTable { if entry.is_empty() { Self::write_entry(&new_entry, i, &mut chunk); log::trace!(target: "parity-db", "{}: Inserted at {}.{}: {}", self.id, chunk_index, i, new_entry.address(self.id.index_bits())); - log.insert_index(self.id, chunk_index, i as u8, &chunk); + log.insert_index(self.id, chunk_index, i as u8, chunk); return Ok(PlanOutcome::Written) } } @@ -472,16 +466,16 @@ impl IndexTable { let key_prefix = TableKey::index_from_partial(key); let chunk_index = self.chunk_index(key_prefix); - if let Some(chunk) = log.with_index(self.id, chunk_index, |chunk| *chunk) { + if let Some(chunk) = log.with_index(self.id, chunk_index, |chunk| chunk.clone()) { return self.plan_insert_chunk(key_prefix, address, chunk, sub_index, log) } if let Some(map) = &*self.map.read() { - let chunk = *Self::chunk_at(chunk_index, map)?; + let chunk = Self::chunk_at(chunk_index, map)?.clone(); return self.plan_insert_chunk(key_prefix, address, chunk, sub_index, log) } - let chunk = EMPTY_CHUNK; + let chunk = EMPTY_CHUNK.clone(); self.plan_insert_chunk(key_prefix, address, chunk, sub_index, log) } @@ -500,7 +494,7 @@ impl IndexTable { if !entry.is_empty() && entry.partial_key(self.id.index_bits()) == partial_key { let new_entry = Entry::empty(); Self::write_entry(&new_entry, i, &mut chunk); - log.insert_index(self.id, chunk_index, i as u8, &chunk); + log.insert_index(self.id, chunk_index, i as u8, chunk); log::trace!(target: "parity-db", "{}: Removed at {}.{}", self.id, chunk_index, i); return Ok(PlanOutcome::Written) } @@ -518,12 +512,12 @@ impl IndexTable { let chunk_index = self.chunk_index(key_prefix); - if let Some(chunk) = log.with_index(self.id, chunk_index, |chunk| *chunk) { + if let Some(chunk) = log.with_index(self.id, chunk_index, |chunk| chunk.clone()) { return self.plan_remove_chunk(key_prefix, chunk, sub_index, log) } if let Some(map) = &*self.map.read() { - let chunk = *Self::chunk_at(chunk_index, map)?; + let chunk = Self::chunk_at(chunk_index, map)?.clone(); return self.plan_remove_chunk(key_prefix, chunk, sub_index, log) } @@ -626,7 +620,7 @@ mod test { #[test] fn test_entries() { - let mut chunk = IndexTable::transmute_chunk(EMPTY_CHUNK); + let mut chunk = IndexTable::transmute_chunk(&EMPTY_CHUNK).clone(); let mut chunk2 = EMPTY_CHUNK; for (i, chunk) in chunk.iter_mut().enumerate().take(CHUNK_ENTRIES) { use std::{ @@ -641,7 +635,7 @@ mod test { *chunk = entry; } - assert!(IndexTable::transmute_chunk(chunk2) == chunk); + assert!(IndexTable::transmute_chunk(&chunk2) == &chunk); } #[test] diff --git a/src/log.rs b/src/log.rs index 2793170e..71bf7784 100644 --- a/src/log.rs +++ b/src/log.rs @@ -405,13 +405,13 @@ impl<'a> LogWriter<'a> { self.log.record_id } - pub fn insert_index(&mut self, table: IndexTableId, index: u64, sub: u8, data: &IndexChunk) { + pub fn insert_index(&mut self, table: IndexTableId, index: u64, sub: u8, data: IndexChunk) { match self.log.local_index.entry(table).or_default().map.entry(index) { std::collections::hash_map::Entry::Occupied(mut entry) => { - *entry.get_mut() = (self.log.record_id, entry.get().1 | (1 << sub), *data); + *entry.get_mut() = (self.log.record_id, entry.get().1 | (1 << sub), data); }, std::collections::hash_map::Entry::Vacant(entry) => { - entry.insert((self.log.record_id, 1 << sub, *data)); + entry.insert((self.log.record_id, 1 << sub, data)); }, } }