1use crate::{
5 column::{ColId, MIN_INDEX_BITS},
6 display::hex,
7 error::{try_io, Error, Result},
8 file::madvise_random,
9 log::{LogQuery, LogReader, LogWriter},
10 parking_lot::{RwLock, RwLockUpgradableReadGuard, RwLockWriteGuard},
11 stats::{self, ColumnStats},
12 table::{key::TableKey, SIZE_TIERS_BITS},
13 Key,
14};
15#[cfg(target_arch = "x86")]
16use std::arch::x86::*;
17#[cfg(target_arch = "x86_64")]
18use std::arch::x86_64::*;
19use std::convert::TryInto;
20
21const CHUNK_LEN: usize = CHUNK_ENTRIES * ENTRY_BYTES; const CHUNK_ENTRIES: usize = 1 << CHUNK_ENTRIES_BITS;
24const CHUNK_ENTRIES_BITS: u8 = 6;
25const HEADER_SIZE: usize = 512;
26const META_SIZE: usize = 16 * 1024; const ENTRY_BITS: u8 = 64;
28pub const ENTRY_BYTES: usize = ENTRY_BITS as usize / 8;
29
30const EMPTY_CHUNK: Chunk = Chunk([0u8; CHUNK_LEN]);
31const EMPTY_ENTRIES: [Entry; CHUNK_ENTRIES] = [Entry::empty(); CHUNK_ENTRIES];
32
33#[repr(C, align(8))]
34#[derive(PartialEq, Eq, Clone, Debug)]
35pub struct Chunk(pub [u8; CHUNK_LEN]);
36
37#[allow(clippy::assertions_on_constants)]
38const _: () = assert!(META_SIZE >= HEADER_SIZE + stats::TOTAL_SIZE);
39
40#[derive(PartialEq, Eq, Clone, Copy)]
41pub struct Entry(u64);
42
43impl Entry {
44 #[inline]
45 fn new(address: Address, partial_key: u64, index_bits: u8) -> Entry {
46 Entry((partial_key << Self::address_bits(index_bits)) | address.as_u64())
47 }
48
49 #[inline]
50 pub fn address_bits(index_bits: u8) -> u8 {
51 index_bits + CHUNK_ENTRIES_BITS + SIZE_TIERS_BITS
53 }
54
55 #[inline]
56 pub fn last_address(index_bits: u8) -> u64 {
57 (1u64 << Self::address_bits(index_bits)) - 1
58 }
59
60 #[inline]
61 pub fn address(&self, index_bits: u8) -> Address {
62 Address::from_u64(self.0 & Self::last_address(index_bits))
63 }
64
65 #[inline]
66 pub fn partial_key(&self, index_bits: u8) -> u64 {
67 self.0 >> Self::address_bits(index_bits)
68 }
69
70 #[inline]
71 fn extract_key(key_prefix: u64, index_bits: u8) -> u64 {
72 (key_prefix << index_bits) >> Self::address_bits(index_bits)
73 }
74
75 #[inline]
76 pub fn is_empty(&self) -> bool {
77 self.0 == 0
78 }
79
80 pub fn as_u64(&self) -> u64 {
81 self.0
82 }
83
84 const fn empty() -> Self {
85 Entry(0)
86 }
87
88 fn from_u64(e: u64) -> Self {
89 Entry(e)
90 }
91}
92
93#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
94pub struct Address(u64);
95
96impl Address {
97 pub const fn new(offset: u64, size_tier: u8) -> Address {
98 Address((offset << SIZE_TIERS_BITS) | size_tier as u64)
99 }
100
101 pub const fn from_u64(a: u64) -> Address {
102 Address(a)
103 }
104
105 pub fn offset(&self) -> u64 {
106 self.0 >> SIZE_TIERS_BITS
107 }
108
109 pub fn size_tier(&self) -> u8 {
110 (self.0 & ((1 << SIZE_TIERS_BITS) as u64 - 1)) as u8
111 }
112
113 pub fn as_u64(&self) -> u64 {
114 self.0
115 }
116}
117
118impl std::fmt::Display for Address {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 write!(f, "addr {:02}:{}", hex(&[self.size_tier()]), self.offset())
121 }
122}
123
124pub enum PlanOutcome {
125 Written,
126 NeedReindex,
127 Skipped,
128}
129
130#[derive(Debug)]
131pub struct IndexTable {
132 pub id: TableId,
133 map: RwLock<Option<memmap2::MmapMut>>,
134 path: std::path::PathBuf,
135}
136
137fn total_entries(index_bits: u8) -> u64 {
138 total_chunks(index_bits) * CHUNK_ENTRIES as u64
139}
140
141fn total_chunks(index_bits: u8) -> u64 {
142 1u64 << index_bits
143}
144
145fn file_size(index_bits: u8) -> u64 {
146 total_entries(index_bits) * 8 + META_SIZE as u64
147}
148
149#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
150pub struct TableId(u16);
151
152impl TableId {
153 pub fn new(col: ColId, index_bits: u8) -> TableId {
154 TableId(((col as u16) << 8) | (index_bits as u16))
155 }
156
157 pub fn from_u16(id: u16) -> TableId {
158 TableId(id)
159 }
160
161 pub fn col(&self) -> ColId {
162 (self.0 >> 8) as ColId
163 }
164
165 pub fn index_bits(&self) -> u8 {
166 (self.0 & 0xff) as u8
167 }
168
169 pub fn file_name(&self) -> String {
170 format!("index_{:02}_{}", self.col(), self.index_bits())
171 }
172
173 pub fn is_file_name(col: ColId, name: &str) -> bool {
174 name.starts_with(&format!("index_{col:02}_"))
175 }
176
177 pub fn as_u16(&self) -> u16 {
178 self.0
179 }
180
181 pub fn total_chunks(&self) -> u64 {
182 total_chunks(self.index_bits())
183 }
184
185 pub fn total_entries(&self) -> u64 {
186 total_entries(self.index_bits())
187 }
188
189 pub fn log_index(&self) -> usize {
190 self.col() as usize * (64 - MIN_INDEX_BITS) as usize + self.index_bits() as usize
191 }
192
193 pub fn from_log_index(i: usize) -> Self {
194 let col = i / (64 - MIN_INDEX_BITS) as usize;
195 let bits = i % (64 - MIN_INDEX_BITS) as usize;
196 TableId::new(col as ColId, bits as u8)
197 }
198
199 pub const fn max_log_indicies(num_columns: usize) -> usize {
200 (64 - MIN_INDEX_BITS) as usize * num_columns
201 }
202}
203
204impl std::fmt::Display for TableId {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 write!(f, "i{:02}-{:02}", self.col(), self.index_bits())
207 }
208}
209
210impl IndexTable {
211 pub fn open_existing(path: &std::path::Path, id: TableId) -> Result<Option<IndexTable>> {
212 let mut path: std::path::PathBuf = path.into();
213 path.push(id.file_name());
214
215 let file = match std::fs::OpenOptions::new().read(true).write(true).open(path.as_path()) {
216 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
217 Err(e) => return Err(Error::Io(e)),
218 Ok(file) => file,
219 };
220
221 try_io!(file.set_len(file_size(id.index_bits())));
222 let mut map = try_io!(unsafe { memmap2::MmapMut::map_mut(&file) });
223 madvise_random(&mut map);
224 log::debug!(target: "parity-db", "Opened existing index {}", id);
225 Ok(Some(IndexTable { id, path, map: RwLock::new(Some(map)) }))
226 }
227
228 pub fn create_new(path: &std::path::Path, id: TableId) -> IndexTable {
229 let mut path: std::path::PathBuf = path.into();
230 path.push(id.file_name());
231 IndexTable { id, path, map: RwLock::new(None) }
232 }
233
234 pub fn load_stats(&self) -> Result<ColumnStats> {
235 if let Some(map) = &*self.map.read() {
236 Ok(ColumnStats::from_slice(try_io!(Ok(
237 &map[HEADER_SIZE..HEADER_SIZE + stats::TOTAL_SIZE]
238 ))))
239 } else {
240 Ok(ColumnStats::empty())
241 }
242 }
243
244 pub fn write_stats(&self, stats: &ColumnStats) -> Result<()> {
245 if let Some(map) = &mut *self.map.write() {
246 let slice = try_io!(Ok(&mut map[HEADER_SIZE..HEADER_SIZE + stats::TOTAL_SIZE]));
247 stats.to_slice(slice);
248 }
249 Ok(())
250 }
251
252 fn chunk_at(index: u64, map: &memmap2::MmapMut) -> Result<&Chunk> {
253 let offset = META_SIZE + index as usize * CHUNK_LEN;
254 let ptr = unsafe { &*(map[offset..offset + CHUNK_LEN].as_ptr() as *const Chunk) };
255 Ok(try_io!(Ok(ptr)))
256 }
257
258 fn chunk_entries_at(index: u64, map: &memmap2::MmapMut) -> Result<&[Entry; CHUNK_ENTRIES]> {
259 let offset = META_SIZE + index as usize * CHUNK_LEN;
260 let ptr = unsafe {
261 &*(map[offset..offset + CHUNK_LEN].as_ptr() as *const [Entry; CHUNK_ENTRIES])
262 };
263 Ok(try_io!(Ok(ptr)))
264 }
265 #[cfg(target_arch = "x86_64")]
266 fn find_entry(&self, key_prefix: u64, sub_index: usize, chunk: &Chunk) -> (Entry, usize) {
267 self.find_entry_sse2(key_prefix, sub_index, chunk)
268 }
269
270 #[cfg(not(target_arch = "x86_64"))]
271 fn find_entry(&self, key_prefix: u64, sub_index: usize, chunk: &Chunk) -> (Entry, usize) {
272 self.find_entry_base(key_prefix, sub_index, chunk)
273 }
274
275 #[cfg(target_arch = "x86_64")]
276 fn find_entry_sse2(&self, key_prefix: u64, sub_index: usize, chunk: &Chunk) -> (Entry, usize) {
277 assert!(chunk.0.len() >= CHUNK_ENTRIES * 8); const _: () = assert!(
279 CHUNK_ENTRIES % 4 == 0,
280 "We assume here we got buffer with a number of elements that is a multiple of 4"
281 );
282
283 let shift = std::cmp::max(32, Entry::address_bits(self.id.index_bits()));
284 let pk = (key_prefix << self.id.index_bits()) >> shift;
285 if pk == 0 {
286 return self.find_entry_base(key_prefix, sub_index, chunk)
288 }
289 unsafe {
290 let target = _mm_set1_epi32(pk as i32);
291 let shift_mask = _mm_set_epi64x(0, shift.into());
292 let mut i = (sub_index >> 2) << 2; let mut skip = (sub_index - i) as i32;
294 while i + 4 <= CHUNK_ENTRIES {
295 let first_two = _mm_shuffle_epi32::<0b11011000>(_mm_srl_epi64(
299 _mm_loadu_si128(chunk.0[i * 8..].as_ptr() as *const __m128i),
300 shift_mask,
301 ));
302 let last_two = _mm_shuffle_epi32::<0b11011000>(_mm_srl_epi64(
303 _mm_loadu_si128(chunk.0[(i + 2) * 8..].as_ptr() as *const __m128i),
304 shift_mask,
305 ));
306 let current = _mm_unpacklo_epi64(first_two, last_two);
308 let cmp = _mm_movemask_epi8(_mm_cmpeq_epi32(current, target)) >> (skip * 4);
309 if cmp != 0 {
310 let position = i + skip as usize + (cmp.trailing_zeros() as usize) / 4;
311 return (Self::read_entry(chunk, position), position)
312 }
313 i += 4;
314 skip = 0;
315 }
316 }
317 (Entry::empty(), 0)
318 }
319
320 fn find_entry_base(&self, key_prefix: u64, sub_index: usize, chunk: &Chunk) -> (Entry, usize) {
321 let partial_key = Entry::extract_key(key_prefix, self.id.index_bits());
322 for i in sub_index..CHUNK_ENTRIES {
323 let entry = Self::read_entry(chunk, i);
324 if entry.partial_key(self.id.index_bits()) == partial_key && !entry.is_empty() {
325 return (entry, i)
326 }
327 }
328 (Entry::empty(), 0)
329 }
330
331 pub fn recover_key_prefix(&self, chunk: u64, entry: Entry) -> Key {
333 let partial_key = entry.partial_key(self.id.index_bits());
335 let k = 64 - Entry::address_bits(self.id.index_bits());
336 let index_key = (chunk << (64 - self.id.index_bits())) |
337 (partial_key << (64 - k - self.id.index_bits()));
338 let mut key = Key::default();
339 key[0..8].copy_from_slice(&index_key.to_be_bytes());
340 key
341 }
342
343 pub fn get(&self, key: &Key, sub_index: usize, log: &impl LogQuery) -> Result<(Entry, usize)> {
344 log::trace!(target: "parity-db", "{}: Querying {}", self.id, hex(key));
345 let key = TableKey::index_from_partial(key);
346 let chunk_index = self.chunk_index(key);
347
348 if let Some(entry) = log.with_index(self.id, chunk_index, |chunk| {
349 log::trace!(target: "parity-db", "{}: Querying overlay at {}", self.id, chunk_index);
350 self.find_entry(key, sub_index, chunk)
351 }) {
352 return Ok(entry)
353 }
354
355 if let Some(map) = &*self.map.read() {
356 log::trace!(target: "parity-db", "{}: Querying chunk at {}", self.id, chunk_index);
357 let chunk = Self::chunk_at(chunk_index, map)?;
358 return Ok(self.find_entry(key, sub_index, chunk))
359 }
360 Ok((Entry::empty(), 0))
361 }
362
363 pub fn entries(&self, chunk_index: u64, log: &impl LogQuery) -> Result<[Entry; CHUNK_ENTRIES]> {
364 if let Some(entry) =
365 log.with_index(self.id, chunk_index, |chunk| *Self::transmute_chunk(chunk))
366 {
367 return Ok(entry)
368 }
369 if let Some(map) = &*self.map.read() {
370 let chunk = Self::chunk_at(chunk_index, map)?;
371 return Ok(*Self::transmute_chunk(chunk))
372 }
373 Ok(EMPTY_ENTRIES)
374 }
375
376 pub fn sorted_entries(&self) -> Result<Vec<Entry>> {
377 log::info!(target: "parity-db", "{}: Loading into memory", self.id);
378 let mut target = Vec::with_capacity(self.id.total_entries() as usize / 2);
379 if let Some(map) = &*self.map.read() {
380 for chunk_index in 0..self.id.total_chunks() {
381 let source = Self::chunk_entries_at(chunk_index, map)?;
382 for e in source {
383 if !e.is_empty() {
384 target.push(*e);
385 }
386 }
387 }
388 }
389 log::info!(target: "parity-db", "{}: Sorting index", self.id);
390 target.sort_unstable_by(|a, b| {
391 let a = a.address(self.id.index_bits());
392 let b = b.address(self.id.index_bits());
393 a.size_tier().cmp(&b.size_tier()).then_with(|| a.offset().cmp(&b.offset()))
394 });
395 Ok(target)
396 }
397
398 #[inline(always)]
399 fn transmute_chunk(chunk: &Chunk) -> &[Entry; CHUNK_ENTRIES] {
400 unsafe { std::mem::transmute(chunk) }
401 }
402
403 #[inline(always)]
404 fn write_entry(entry: &Entry, at: usize, chunk: &mut Chunk) {
405 chunk.0[at * 8..at * 8 + 8].copy_from_slice(&entry.as_u64().to_le_bytes());
406 }
407
408 #[inline(always)]
409 fn read_entry(chunk: &Chunk, at: usize) -> Entry {
410 Entry::from_u64(u64::from_le_bytes(chunk.0[at * 8..at * 8 + 8].try_into().unwrap()))
411 }
412
413 #[inline(always)]
414 fn chunk_index(&self, key_prefix: u64) -> u64 {
415 key_prefix >> (ENTRY_BITS - self.id.index_bits())
416 }
417
418 fn plan_insert_chunk(
419 &self,
420 key_prefix: u64,
421 address: Address,
422 mut chunk: Chunk,
423 sub_index: Option<usize>,
424 log: &mut LogWriter,
425 ) -> Result<PlanOutcome> {
426 let chunk_index = self.chunk_index(key_prefix);
427 if address.as_u64() > Entry::last_address(self.id.index_bits()) {
428 log::warn!(target: "parity-db", "{}: Address space overflow at {}: {}", self.id, chunk_index, address);
430 return Ok(PlanOutcome::NeedReindex)
431 }
432 let partial_key = Entry::extract_key(key_prefix, self.id.index_bits());
433 let new_entry = Entry::new(address, partial_key, self.id.index_bits());
434 if let Some(i) = sub_index {
435 let entry = Self::read_entry(&chunk, i);
436 assert_eq!(
437 entry.partial_key(self.id.index_bits()),
438 new_entry.partial_key(self.id.index_bits())
439 );
440 Self::write_entry(&new_entry, i, &mut chunk);
441 log::trace!(target: "parity-db", "{}: Replaced at {}.{}: {}", self.id, chunk_index, i, new_entry.address(self.id.index_bits()));
442 log.insert_index(self.id, chunk_index, i as u8, chunk);
443 return Ok(PlanOutcome::Written)
444 }
445 for i in 0..CHUNK_ENTRIES {
446 let entry = Self::read_entry(&chunk, i);
447 if entry.is_empty() {
448 Self::write_entry(&new_entry, i, &mut chunk);
449 log::trace!(target: "parity-db", "{}: Inserted at {}.{}: {}", self.id, chunk_index, i, new_entry.address(self.id.index_bits()));
450 log.insert_index(self.id, chunk_index, i as u8, chunk);
451 return Ok(PlanOutcome::Written)
452 }
453 }
454 log::debug!(target: "parity-db", "{}: Index chunk full at {}", self.id, chunk_index);
455 Ok(PlanOutcome::NeedReindex)
456 }
457
458 pub fn write_insert_plan(
459 &self,
460 key: &Key,
461 address: Address,
462 sub_index: Option<usize>,
463 log: &mut LogWriter,
464 ) -> Result<PlanOutcome> {
465 log::trace!(target: "parity-db", "{}: Inserting {} -> {}", self.id, hex(key), address);
466 let key_prefix = TableKey::index_from_partial(key);
467 let chunk_index = self.chunk_index(key_prefix);
468
469 if let Some(chunk) = log.with_index(self.id, chunk_index, |chunk| chunk.clone()) {
470 return self.plan_insert_chunk(key_prefix, address, chunk, sub_index, log)
471 }
472
473 if let Some(map) = &*self.map.read() {
474 let chunk = Self::chunk_at(chunk_index, map)?.clone();
475 return self.plan_insert_chunk(key_prefix, address, chunk, sub_index, log)
476 }
477
478 let chunk = EMPTY_CHUNK.clone();
479 self.plan_insert_chunk(key_prefix, address, chunk, sub_index, log)
480 }
481
482 fn plan_remove_chunk(
483 &self,
484 key_prefix: u64,
485 mut chunk: Chunk,
486 sub_index: usize,
487 log: &mut LogWriter,
488 ) -> Result<PlanOutcome> {
489 let chunk_index = self.chunk_index(key_prefix);
490 let partial_key = Entry::extract_key(key_prefix, self.id.index_bits());
491
492 let i = sub_index;
493 let entry = Self::read_entry(&chunk, i);
494 if !entry.is_empty() && entry.partial_key(self.id.index_bits()) == partial_key {
495 let new_entry = Entry::empty();
496 Self::write_entry(&new_entry, i, &mut chunk);
497 log.insert_index(self.id, chunk_index, i as u8, chunk);
498 log::trace!(target: "parity-db", "{}: Removed at {}.{}", self.id, chunk_index, i);
499 return Ok(PlanOutcome::Written)
500 }
501 Ok(PlanOutcome::Skipped)
502 }
503
504 pub fn write_remove_plan(
505 &self,
506 key: &Key,
507 sub_index: usize,
508 log: &mut LogWriter,
509 ) -> Result<PlanOutcome> {
510 log::trace!(target: "parity-db", "{}: Removing {}", self.id, hex(key));
511 let key_prefix = TableKey::index_from_partial(key);
512
513 let chunk_index = self.chunk_index(key_prefix);
514
515 if let Some(chunk) = log.with_index(self.id, chunk_index, |chunk| chunk.clone()) {
516 return self.plan_remove_chunk(key_prefix, chunk, sub_index, log)
517 }
518
519 if let Some(map) = &*self.map.read() {
520 let chunk = Self::chunk_at(chunk_index, map)?.clone();
521 return self.plan_remove_chunk(key_prefix, chunk, sub_index, log)
522 }
523
524 Ok(PlanOutcome::Skipped)
525 }
526
527 pub fn enact_plan(&self, index: u64, log: &mut LogReader) -> Result<()> {
528 let mut map = self.map.upgradable_read();
529 if map.is_none() {
530 let mut wmap = RwLockUpgradableReadGuard::upgrade(map);
531 let file = try_io!(std::fs::OpenOptions::new()
532 .write(true)
533 .read(true)
534 .create_new(true)
535 .open(self.path.as_path()));
536 log::debug!(target: "parity-db", "Created new index {}", self.id);
537 try_io!(file.set_len(file_size(self.id.index_bits())));
538 let mut mmap = try_io!(unsafe { memmap2::MmapMut::map_mut(&file) });
539 madvise_random(&mut mmap);
540 *wmap = Some(mmap);
541 map = RwLockWriteGuard::downgrade_to_upgradable(wmap);
542 }
543
544 let map = map.as_ref().unwrap();
545 let offset = META_SIZE + index as usize * CHUNK_LEN;
546 let ptr: *mut u8 = map.as_ptr() as *mut u8;
549 let chunk: &mut [u8] = unsafe {
550 let ptr = ptr.add(offset);
551 std::slice::from_raw_parts_mut(ptr, CHUNK_LEN)
552 };
553 let mut mask_buf = [0u8; 8];
554 log.read(&mut mask_buf)?;
555 let mut mask = u64::from_le_bytes(mask_buf);
556 while mask != 0 {
557 let i = mask.trailing_zeros();
558 mask &= !(1 << i);
559 log.read(try_io!(Ok(
560 &mut chunk[i as usize * ENTRY_BYTES..(i as usize + 1) * ENTRY_BYTES]
561 )))?;
562 }
563 log::trace!(target: "parity-db", "{}: Enacted chunk {}", self.id, index);
564 Ok(())
565 }
566
567 pub fn validate_plan(&self, index: u64, log: &mut LogReader) -> Result<()> {
568 if index >= self.id.total_entries() {
569 return Err(Error::Corruption("Bad index".into()))
570 }
571 let mut buf = [0u8; 8];
572 log.read(&mut buf)?;
573 let mut mask = u64::from_le_bytes(buf);
574 while mask != 0 {
575 let i = mask.trailing_zeros();
576 mask &= !(1 << i);
577 log.read(&mut buf[..])?;
578 }
579 log::trace!(target: "parity-db", "{}: Validated chunk {}", self.id, index);
580 Ok(())
581 }
582
583 pub fn skip_plan(log: &mut LogReader) -> Result<()> {
584 let mut buf = [0u8; 8];
585 log.read(&mut buf)?;
586 let mut mask = u64::from_le_bytes(buf);
587 while mask != 0 {
588 let i = mask.trailing_zeros();
589 mask &= !(1 << i);
590 log.read(&mut buf[..])?;
591 }
592 Ok(())
593 }
594
595 pub fn drop_file(self) -> Result<()> {
596 drop(self.map);
597 try_io!(std::fs::remove_file(self.path.as_path()));
598 log::debug!(target: "parity-db", "{}: Dropped table", self.id);
599 Ok(())
600 }
601
602 pub fn flush(&self) -> Result<()> {
603 if let Some(map) = &*self.map.read() {
604 try_io!(map.flush_range(META_SIZE, map.len() - META_SIZE));
606 }
607 Ok(())
608 }
609}
610
611#[cfg(test)]
612mod test {
613 use super::*;
614 use rand::{Rng, SeedableRng};
615 use std::path::PathBuf;
616 #[cfg(feature = "bench")]
617 use test::Bencher;
618 #[cfg(feature = "bench")]
619 extern crate test;
620
621 #[test]
622 fn test_entries() {
623 let mut chunk = IndexTable::transmute_chunk(&EMPTY_CHUNK).clone();
624 let mut chunk2 = EMPTY_CHUNK;
625 for (i, chunk) in chunk.iter_mut().enumerate().take(CHUNK_ENTRIES) {
626 use std::{
627 collections::hash_map::DefaultHasher,
628 hash::{Hash, Hasher},
629 };
630 let mut hasher = DefaultHasher::new();
631 i.hash(&mut hasher);
632 let hash = hasher.finish();
633 let entry = Entry::from_u64(hash);
634 IndexTable::write_entry(&entry, i, &mut chunk2);
635 *chunk = entry;
636 }
637
638 assert!(IndexTable::transmute_chunk(&chunk2) == &chunk);
639 }
640
641 #[test]
642 fn test_find_entries() {
643 let partial_keys = [1, 1 << 10, 1 << 20];
644 for index_bits in [16, 18, 20, 22] {
645 let index_table = IndexTable {
646 id: TableId(index_bits.into()),
647 map: RwLock::new(None),
648 path: PathBuf::new(),
649 };
650
651 let data_address = Address::from_u64((1 << index_bits) - 1);
652
653 let mut chunk = Chunk([0; CHUNK_ENTRIES * 8]);
654 for (i, partial_key) in partial_keys.iter().enumerate() {
655 chunk.0[i * 8..(i + 1) * 8].copy_from_slice(
656 &Entry::new(data_address, *partial_key, index_bits).as_u64().to_le_bytes(),
657 );
658 }
659
660 for partial_key in &partial_keys {
661 let key_prefix = *partial_key << (CHUNK_ENTRIES_BITS + SIZE_TIERS_BITS);
662 #[cfg(target_arch = "x86_64")]
663 assert_eq!(
664 index_table.find_entry_sse2(key_prefix, 0, &chunk).0.partial_key(index_bits),
665 *partial_key
666 );
667 assert_eq!(
668 index_table.find_entry_base(key_prefix, 0, &chunk).0.partial_key(index_bits),
669 *partial_key
670 );
671 }
672 }
673 }
674
675 #[test]
676 fn test_find_any_entry() {
677 let table =
678 IndexTable { id: TableId(18), map: RwLock::new(None), path: Default::default() };
679 let mut chunk = Chunk([0u8; CHUNK_LEN]);
680 let mut entries = [Entry::empty(); CHUNK_ENTRIES];
681 let mut keys = [0u64; CHUNK_ENTRIES];
682 let mut rng = rand::prelude::SmallRng::from_seed(Default::default());
683 for i in 0..CHUNK_ENTRIES {
684 keys[i] = rng.gen();
685 let partial_key = Entry::extract_key(keys[i], 18);
686 let e = Entry::new(Address::new(0, 0), partial_key, 18);
687 entries[i] = e;
688 IndexTable::write_entry(&e, i, &mut chunk);
689 }
690
691 for target in 0..CHUNK_ENTRIES {
692 for start_pos in 0..CHUNK_ENTRIES {
693 let (e, i) = table.find_entry_base(keys[target], start_pos, &chunk);
694 if start_pos <= target {
695 assert_eq!((e.as_u64(), i), (entries[target].as_u64(), target));
696 } else {
697 assert_eq!((e.as_u64(), i), (Entry::empty().as_u64(), 0));
698 }
699 #[cfg(target_arch = "x86_64")]
700 {
701 let (e, i) = table.find_entry_sse2(keys[target], start_pos, &chunk);
702 if start_pos <= target {
703 assert_eq!((e.as_u64(), i), (entries[target].as_u64(), target));
704 } else {
705 assert_eq!((e.as_u64(), i), (Entry::empty().as_u64(), 0));
706 }
707 }
708 }
709 }
710 }
711
712 #[test]
713 fn test_find_entry_same_value() {
714 let table =
715 IndexTable { id: TableId(18), map: RwLock::new(None), path: Default::default() };
716 let mut chunk = Chunk([0u8; CHUNK_LEN]);
717 let key = 0x4242424242424242;
718 let partial_key = Entry::extract_key(key, 18);
719 let entry = Entry::new(Address::new(0, 0), partial_key, 18);
720 for i in 0..CHUNK_ENTRIES {
721 IndexTable::write_entry(&entry, i, &mut chunk);
722 }
723
724 for start_pos in 0..CHUNK_ENTRIES {
725 let (_, i) = table.find_entry_base(key, start_pos, &chunk);
726 assert_eq!(i, start_pos);
727 #[cfg(target_arch = "x86_64")]
728 {
729 let (_, i) = table.find_entry_sse2(key, start_pos, &chunk);
730 assert_eq!(i, start_pos);
731 }
732 }
733 }
734
735 #[test]
736 fn test_find_entry_zero_pk() {
737 let table =
738 IndexTable { id: TableId(16), map: RwLock::new(None), path: Default::default() };
739 let mut chunk = Chunk([0u8; CHUNK_LEN]);
740 let zero_key = 0x0000000000000000;
741 let entry = Entry::new(Address::new(1, 1), zero_key, 16);
742
743 IndexTable::write_entry(&entry, 1, &mut chunk);
745
746 let (_, i) = table.find_entry_base(zero_key, 0, &chunk);
747 assert_eq!(i, 1);
748 #[cfg(target_arch = "x86_64")]
749 {
750 let (_, i) = table.find_entry_sse2(zero_key, 0, &chunk);
751 assert_eq!(i, 1);
752 }
753 }
754
755 #[cfg(feature = "bench")]
756 fn bench_find_entry_internal<
757 F: Fn(&IndexTable, u64, usize, &[u8; CHUNK_LEN]) -> (Entry, usize),
758 >(
759 b: &mut Bencher,
760 f: F,
761 ) {
762 let table =
763 IndexTable { id: TableId(18), map: RwLock::new(None), path: Default::default() };
764 let mut chunk = Chunk([0u8; CHUNK_LEN]);
765 let mut keys = [0u64; CHUNK_ENTRIES];
766 let mut rng = rand::prelude::SmallRng::from_seed(Default::default());
767 for i in 0..CHUNK_ENTRIES {
768 keys[i] = rng.gen();
769 let partial_key = Entry::extract_key(keys[i], 18);
770 let e = Entry::new(Address::new(0, 0), partial_key, 18);
771 IndexTable::write_entry(&e, i, &mut chunk);
772 }
773
774 let mut index = 0;
775 b.iter(|| {
776 let x = f(&table, keys[index], 0, &chunk).1;
777 assert_eq!(x, index);
778 index = (index + 1) % CHUNK_ENTRIES;
779 });
780 }
781
782 #[cfg(feature = "bench")]
783 #[bench]
784 fn bench_find_entry(b: &mut Bencher) {
785 bench_find_entry_internal(b, IndexTable::find_entry_base)
786 }
787
788 #[cfg(feature = "bench")]
789 #[cfg(target_arch = "x86_64")]
790 #[bench]
791 fn bench_find_entry_sse(b: &mut Bencher) {
792 bench_find_entry_internal(b, IndexTable::find_entry_sse2)
793 }
794}