parity_db/
index.rs

1// Copyright 2021-2022 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or MIT.
3
4use 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
21// Index chunk consists of 8 64-bit entries.
22const CHUNK_LEN: usize = CHUNK_ENTRIES * ENTRY_BYTES; // 512 bytes
23const CHUNK_ENTRIES: usize = 1 << CHUNK_ENTRIES_BITS;
24const CHUNK_ENTRIES_BITS: u8 = 6;
25const HEADER_SIZE: usize = 512;
26const META_SIZE: usize = 16 * 1024; // Contains header and column stats
27const 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		// with n index bits there are n * 64 possible entries and 256 size tiers
52		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); // Bound checking (not done by SIMD instructions)
278		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			// Fallback to base version when partial key is zero and would match empty entries.
287			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; // We keep an alignment of 4
293			let mut skip = (sub_index - i) as i32;
294			while i + 4 <= CHUNK_ENTRIES {
295				// We load the value 2 by 2
296				// Then we remove the address by shifting such that the partial key is in the low
297				// part
298				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				// We set into current the input low parts
307				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	// Only returns 54 bits of the actual key.
332	pub fn recover_key_prefix(&self, chunk: u64, entry: Entry) -> Key {
333		// Restore first 54 bits of the key.
334		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			// Address overflow
429			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		// Nasty mutable pointer cast. We do ensure that all chunks that are being written are
547		// accessed through the overlay in other threads.
548		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			// Flush everything except stats.
605			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		// Write at index 1. Index 0 contains an empty entry.
744		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}