Add basic Skymap methods
parent
8fb8d60b4f
commit
db60133dc3
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Created on Mon Aug 09 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Created on Mon Aug 09 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#![allow(dead_code)] // TODO(@ohsayan): Remove this lint once we're done
|
||||
#![allow(clippy::manual_map)] // avoid LLVM bloat
|
||||
|
||||
use core::borrow::Borrow;
|
||||
use core::hash::BuildHasher;
|
||||
use core::hash::Hash;
|
||||
use core::hash::Hasher;
|
||||
use core::mem;
|
||||
use parking_lot::RwLock;
|
||||
use parking_lot::RwLockReadGuard;
|
||||
use parking_lot::RwLockWriteGuard;
|
||||
use std::collections::hash_map::RandomState;
|
||||
|
||||
type LowMap<K, V> = hashbrown::raw::RawTable<(K, V)>;
|
||||
type ShardSlice<K, V> = [RwLock<LowMap<K, V>>];
|
||||
type SRlock<'a, K, V> = RwLockReadGuard<'a, hashbrown::raw::RawTable<(K, V)>>;
|
||||
type SWlock<'a, K, V> = RwLockWriteGuard<'a, hashbrown::raw::RawTable<(K, V)>>;
|
||||
const BITS_IN_USIZE: usize = mem::size_of::<usize>() * 8;
|
||||
const DEFAULT_CAP: usize = 128;
|
||||
|
||||
fn make_hash<K, Q, S>(hash_builder: &S, val: &Q) -> u64
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + ?Sized,
|
||||
S: BuildHasher,
|
||||
{
|
||||
let mut state = hash_builder.build_hasher();
|
||||
val.hash(&mut state);
|
||||
state.finish()
|
||||
}
|
||||
|
||||
fn make_insert_hash<K, S>(hash_builder: &S, val: &K) -> u64
|
||||
where
|
||||
K: Hash,
|
||||
S: BuildHasher,
|
||||
{
|
||||
let mut state = hash_builder.build_hasher();
|
||||
val.hash(&mut state);
|
||||
state.finish()
|
||||
}
|
||||
|
||||
fn make_hasher<K, Q, V, S>(hash_builder: &S) -> impl Fn(&(Q, V)) -> u64 + '_
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash,
|
||||
S: BuildHasher,
|
||||
{
|
||||
move |val| make_hash::<K, Q, S>(hash_builder, &val.0)
|
||||
}
|
||||
|
||||
fn ceq<Q, K, V>(k: &Q) -> impl Fn(&(K, V)) -> bool + '_
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: ?Sized + Eq,
|
||||
{
|
||||
move |x| k.eq(x.0.borrow())
|
||||
}
|
||||
|
||||
fn get_shard_count() -> usize {
|
||||
(num_cpus::get() * 4).next_power_of_two()
|
||||
}
|
||||
|
||||
const fn cttz(amount: usize) -> usize {
|
||||
amount.trailing_zeros() as usize
|
||||
}
|
||||
|
||||
pub struct Skymap<K, V, S = RandomState> {
|
||||
shards: Box<ShardSlice<K, V>>,
|
||||
hasher: S,
|
||||
shift: usize,
|
||||
}
|
||||
|
||||
impl<K, V> Default for Skymap<K, V, RandomState> {
|
||||
fn default() -> Self {
|
||||
Self::with_hasher(RandomState::default())
|
||||
}
|
||||
}
|
||||
|
||||
// basic impls
|
||||
impl<K, V, S> Skymap<K, V, S>
|
||||
where
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self::with_hasher(S::default())
|
||||
}
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
Self::with_capacity_and_hasher(cap, S::default())
|
||||
}
|
||||
pub fn with_capacity_and_hasher(mut cap: usize, hasher: S) -> Self {
|
||||
let shard_count = get_shard_count();
|
||||
let shift = BITS_IN_USIZE - cttz(shard_count);
|
||||
if cap != 0 {
|
||||
cap = (cap + (shard_count - 1)) & !(shard_count - 1);
|
||||
}
|
||||
|
||||
let cap_per_shard = cap / shard_count;
|
||||
Self {
|
||||
shards: (0..shard_count)
|
||||
.map(|_| RwLock::new(LowMap::with_capacity(cap_per_shard)))
|
||||
.collect(),
|
||||
hasher,
|
||||
shift,
|
||||
}
|
||||
}
|
||||
pub fn with_hasher(hasher: S) -> Self {
|
||||
Self::with_capacity_and_hasher(DEFAULT_CAP, hasher)
|
||||
}
|
||||
}
|
||||
|
||||
// const impls
|
||||
impl<K, V, S> Skymap<K, V, S> {
|
||||
const fn shards(&self) -> &ShardSlice<K, V> {
|
||||
&self.shards
|
||||
}
|
||||
const fn determine_shard(&self, hash: usize) -> usize {
|
||||
(hash << 7) >> self.shift
|
||||
}
|
||||
const fn h(&self) -> &S {
|
||||
&self.hasher
|
||||
}
|
||||
}
|
||||
|
||||
// insert/get/remove impls
|
||||
|
||||
impl<K, V, S> Skymap<K, V, S>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
S: BuildHasher + Clone,
|
||||
{
|
||||
pub fn insert(&self, k: K, v: V) -> Option<V> {
|
||||
let hash = make_insert_hash::<K, S>(&self.hasher, &k);
|
||||
let idx = self.determine_shard(hash as usize);
|
||||
unsafe {
|
||||
// begin critical section
|
||||
let mut lowtable = self.get_wshard_unchecked(idx);
|
||||
if let Some((_, item)) = lowtable.get_mut(hash, ceq(&k)) {
|
||||
Some(mem::replace(item, v))
|
||||
} else {
|
||||
lowtable.insert(hash, (k, v), make_hasher::<K, _, V, S>(self.h()));
|
||||
None
|
||||
}
|
||||
// end critical section
|
||||
}
|
||||
}
|
||||
pub fn remove<Q>(&self, k: &Q) -> Option<(K, V)>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
{
|
||||
let hash = make_hash::<K, Q, S>(self.h(), k);
|
||||
let idx = self.determine_shard(hash as usize);
|
||||
unsafe {
|
||||
// begin critical section
|
||||
let mut lowtable = self.get_wshard_unchecked(idx);
|
||||
match lowtable.remove_entry(hash, ceq(k)) {
|
||||
Some(kv) => Some(kv),
|
||||
None => None,
|
||||
}
|
||||
// end critical section
|
||||
}
|
||||
}
|
||||
pub fn remove_if<Q>(&self, k: &Q, f: impl Fn(&(K, V)) -> bool) -> Option<(K, V)>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
{
|
||||
let hash = make_hash::<K, Q, S>(self.h(), k);
|
||||
let idx = self.determine_shard(hash as usize);
|
||||
unsafe {
|
||||
// begin critical section
|
||||
let mut lowtable = self.get_wshard_unchecked(idx);
|
||||
match lowtable.find(hash, ceq(k)) {
|
||||
Some(bucket) => {
|
||||
if f(bucket.as_ref()) {
|
||||
Some(lowtable.remove(bucket))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
// end critical section
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inner impls
|
||||
impl<K, V, S> Skymap<K, V, S> {
|
||||
unsafe fn get_rshard_unchecked(&self, shard: usize) -> SRlock<K, V> {
|
||||
self.shards.get_unchecked(shard).read()
|
||||
}
|
||||
unsafe fn get_wshard_unchecked(&self, shard: usize) -> SWlock<K, V> {
|
||||
self.shards.get_unchecked(shard).write()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_remove() {
|
||||
let map = Skymap::default();
|
||||
map.insert("hello", "world");
|
||||
assert_eq!(map.remove("hello").unwrap().1, "world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_if() {
|
||||
let map = Skymap::default();
|
||||
map.insert("hello", "world");
|
||||
assert!(map
|
||||
.remove_if("hello", |(_k, v)| { (*v).eq("notworld") })
|
||||
.is_none());
|
||||
}
|
Loading…
Reference in New Issue