#pragma once
|
|
#include <cstddef>
|
|
#include <array>
|
|
#include <utility>
|
|
#include <atomic>
|
|
#include <new>
|
|
#include <memory>
|
|
#include <cassert>
|
|
#include <optional>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
using namespace std::chrono_literals;
|
|
/**
|
|
Pensé en un mundo sin memoria, sin tiempo; consideré la posibilidad de un lenguaje
|
|
que ignorara los sustantivos, un lenguaje de verbos impersonales y de indeclinables
|
|
epítetos. Así fueron muriendo los días y con los días los años, pero algo parecido
|
|
a la felicidad ocurrió una mañana. Llovió, con lentitud poderosa.
|
|
**/
|
|
|
|
namespace mct20 {
|
|
|
|
template<typename T>
|
|
class accessor {
|
|
public:
|
|
accessor(const T* ptr, std::atomic<unsigned int>& incremented_ref)
|
|
: pointer(ptr)
|
|
, reference_cnt(incremented_ref)
|
|
{
|
|
assert(reference_cnt.load() != 0);
|
|
}
|
|
|
|
accessor(const accessor& a)
|
|
: pointer(a.pointer)
|
|
, reference_cnt(a.reference_cnt)
|
|
{
|
|
reference_cnt.fetch_add(1);
|
|
}
|
|
|
|
accessor(const accessor&& a)
|
|
: pointer(a.pointer)
|
|
, reference_cnt(a.reference_cnt)
|
|
{
|
|
reference_cnt.fetch_add(1);
|
|
}
|
|
|
|
operator const T&() {
|
|
return *pointer;
|
|
}
|
|
~accessor() {
|
|
reference_cnt.fetch_sub(1);
|
|
}
|
|
private:
|
|
const T* pointer;
|
|
std::atomic<unsigned int>& reference_cnt;
|
|
};
|
|
|
|
namespace _details_ {
|
|
#ifdef __cpp_lib_hardware_interference_size
|
|
constexpr size_t predictable_padding = std::hardware_constructive_interference_size;
|
|
#else
|
|
// Wild guess, may be suboptimal or plain wrong
|
|
constexpr size_t predictable_padding = 128;
|
|
#endif
|
|
|
|
size_t rotl(size_t a, uint8_t b) {
|
|
b%=sizeof(size_t)*8;
|
|
return a << b | a >> (sizeof(size_t)*8 - b);
|
|
}
|
|
|
|
template<size_t against>
|
|
constexpr size_t alignment =
|
|
(against%predictable_padding!=0)*predictable_padding
|
|
+ (against/predictable_padding)*predictable_padding;
|
|
|
|
template<typename K, typename V>
|
|
class bucket {
|
|
constexpr static uint32_t delete_mode = 65536;
|
|
std::atomic<uint32_t> delete_lock;
|
|
|
|
void reader_lock() {
|
|
while(delete_lock.fetch_add(1) >= delete_mode) {
|
|
delete_lock.fetch_sub(1);
|
|
std::this_thread::yield();
|
|
}
|
|
return;
|
|
}
|
|
|
|
void writer_lock() {
|
|
while(delete_lock.fetch_add(delete_mode) >= delete_mode) {
|
|
delete_lock.fetch_sub(delete_mode);
|
|
std::this_thread::yield();
|
|
}
|
|
while(delete_lock.load() != delete_mode) {
|
|
}
|
|
return;
|
|
}
|
|
|
|
void reader_unlock() {
|
|
delete_lock.fetch_sub(1);
|
|
}
|
|
|
|
void writer_unlock() {
|
|
delete_lock.fetch_sub(delete_mode);
|
|
}
|
|
|
|
struct RGuard {
|
|
bucket& master;
|
|
RGuard(bucket& m)
|
|
: master(m)
|
|
{master.reader_lock();}
|
|
|
|
~RGuard()
|
|
{master.reader_unlock();}
|
|
};
|
|
|
|
struct WGuard {
|
|
bucket& master;
|
|
WGuard(bucket& m)
|
|
: master(m)
|
|
{master.writer_lock();}
|
|
|
|
~WGuard()
|
|
{master.writer_unlock();}
|
|
};
|
|
public:
|
|
bucket()
|
|
: start{nullptr}
|
|
{}
|
|
|
|
void remove(size_t hash, const K& key) {
|
|
WGuard _g(*this);
|
|
auto it = start.load();
|
|
auto prev = &start;
|
|
do{
|
|
if(it == nullptr) return;
|
|
while(it->contents.hash != hash)
|
|
{
|
|
prev = reinterpret_cast<node_ptr*>(&(it->contents.next));
|
|
it = (node*)it->contents.next.load();
|
|
if(it == nullptr) return;
|
|
}
|
|
if(it->contents.key == key) {
|
|
prev->store(reinterpret_cast<node*>(it->contents.next.load()));
|
|
it->contents.references.fetch_sub(1);
|
|
while(it->contents.references.load()!=0) {
|
|
std::this_thread::yield();
|
|
}
|
|
delete it->contents.ptr;
|
|
delete it;
|
|
return;
|
|
}
|
|
prev = reinterpret_cast<node_ptr*>(&(it->contents.next));
|
|
it = (node*)it->contents.next.load();
|
|
} while(true);
|
|
}
|
|
|
|
void push(size_t hash, const K& key, const V& value) {
|
|
RGuard _g(*this);
|
|
auto t = new node{
|
|
.contents = node_contents{
|
|
.key{key},
|
|
.ptr{new V{value}},
|
|
.hash{hash},
|
|
.references{1}
|
|
}
|
|
};
|
|
t->contents.next.store(t);
|
|
node* expect;
|
|
do {
|
|
expect = start.load();
|
|
t->contents.next.store(expect);
|
|
} while(
|
|
!std::atomic_compare_exchange_strong(
|
|
&start,
|
|
&expect,
|
|
t
|
|
)
|
|
);
|
|
}
|
|
|
|
std::optional<accessor<V>> get(const size_t hash, const K& key) {
|
|
RGuard _g(*this);
|
|
auto v = start.load();
|
|
while(v) {
|
|
if(v->contents.references.fetch_add(1)!=0)
|
|
{
|
|
if(v->contents.hash == hash) {
|
|
if(v->contents.key == key) {
|
|
return accessor<V>(
|
|
v->contents.ptr,
|
|
v->contents.references
|
|
);
|
|
} else {
|
|
auto n = reinterpret_cast<node*>(v->contents.next.load());
|
|
v->contents.references.fetch_sub(1);
|
|
v = n;
|
|
}
|
|
} else {
|
|
auto n = reinterpret_cast<node*>(v->contents.next.load());
|
|
v->contents.references.fetch_sub(1);
|
|
v = n;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto n = reinterpret_cast<node*>(v->contents.next.load());
|
|
v->contents.references.fetch_sub(1);
|
|
v = n;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
struct node_contents{
|
|
std::atomic<void*> next;
|
|
const K key;
|
|
const V* ptr;
|
|
size_t hash;
|
|
std::atomic<unsigned int> references;
|
|
};
|
|
|
|
using node = union {
|
|
alignas(alignment<sizeof(node_contents)>) node_contents contents;
|
|
};
|
|
|
|
using node_ptr = std::atomic<node*>;
|
|
|
|
node_ptr start;
|
|
};
|
|
}
|
|
|
|
template<typename K, typename V, size_t bucket_count, typename hash = std::hash<K>>
|
|
class lfhmap {
|
|
using bucket = _details_::bucket<K, V>;
|
|
|
|
public:
|
|
std::optional<accessor<V>> get(const K& key) {
|
|
auto l = hash{}(key);
|
|
auto ret = buckets[l%bucket_count].get(l, key);
|
|
if(ret) return ret;
|
|
l = _details_::rotl(l, sizeof(size_t)*4);
|
|
return buckets[l%bucket_count].get(l, key);
|
|
}
|
|
|
|
void set(const K& key, const V& value) {
|
|
const auto l = hash{}(key);
|
|
auto& ref = buckets[l%bucket_count];
|
|
if(ref.start.load() == nullptr)
|
|
{
|
|
ref.push(l, key, value);
|
|
return;
|
|
}
|
|
const auto l2 = _details_::rotl(l, sizeof(size_t)*4);
|
|
auto& ref2 = buckets[l2%bucket_count];
|
|
if(ref2.start.load() == nullptr)
|
|
{
|
|
ref2.push(l2, key, value);
|
|
return;
|
|
}
|
|
if((l^l2)&1) {
|
|
ref.push(l, key, value);
|
|
} else {
|
|
ref2.push(l2, key, value);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void remove(const K& key) {
|
|
const auto l = hash{}(key);
|
|
auto& ref = buckets[l%bucket_count];
|
|
ref.remove(l, key);
|
|
const auto l2 = _details_::rotl(l, sizeof(size_t)*4);
|
|
auto& ref2 = buckets[l2%bucket_count];
|
|
ref2.remove(l2, key);
|
|
return;
|
|
}
|
|
|
|
lfhmap() {
|
|
for(auto& a : buckets) {
|
|
a.start = nullptr;
|
|
}
|
|
}
|
|
private:
|
|
std::array<bucket, bucket_count> buckets;
|
|
};
|
|
|
|
}
|