@ -0,0 +1 @@ | |||||
a.out |
@ -0,0 +1,16 @@ | |||||
{ | |||||
// Use IntelliSense to learn about possible attributes. | |||||
// Hover to view descriptions of existing attributes. | |||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | |||||
"version": "0.2.0", | |||||
"configurations": [ | |||||
{ | |||||
"name": "Debug", | |||||
"type": "gdb", | |||||
"request": "launch", | |||||
"target": "./a.out", | |||||
"cwd": "${workspaceRoot}", | |||||
"valuesFormatting": "parseText" | |||||
} | |||||
] | |||||
} |
@ -0,0 +1,6 @@ | |||||
{ | |||||
"clang.cxxflags": [ | |||||
"-std=c++17", | |||||
"-I${workspaceRoot}/include" | |||||
] | |||||
} |
@ -0,0 +1,198 @@ | |||||
#pragma once | |||||
#include <cstddef> | |||||
#include <array> | |||||
#include <utility> | |||||
#include <atomic> | |||||
#include <new> | |||||
#include <memory> | |||||
#include <cassert> | |||||
#include <optional> | |||||
/** | |||||
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 { | |||||
public: | |||||
bucket() | |||||
: start{nullptr} | |||||
{} | |||||
void push(size_t hash, const K& key, const V& value) { | |||||
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) { | |||||
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; | |||||
} | |||||
lfhmap() { | |||||
for(auto& a : buckets) { | |||||
a.start = nullptr; | |||||
} | |||||
} | |||||
private: | |||||
std::array<bucket, bucket_count> buckets; | |||||
}; | |||||
} |
@ -0,0 +1,13 @@ | |||||
#!/bin/bash | |||||
g++ -pthread -Iinclude -std=c++17 -O3 -g tests/test01.cpp | |||||
./a.out || echo "FAILURE ON TEST01" | |||||
g++ -pthread -Iinclude -std=c++17 -O3 -g tests/test02.cpp | |||||
./a.out || echo "FAILURE ON TEST02" | |||||
g++ -pthread -Iinclude -std=c++17 -O0 -g tests/test03.cpp | |||||
./a.out || echo "FAILURE ON TEST03" | |||||
g++ -pthread -Iinclude -std=c++17 -O0 -g tests/test04.cpp | |||||
./a.out || echo "FAILURE ON TEST04" |
@ -0,0 +1,21 @@ | |||||
#include "lfhmap.hpp" | |||||
#include <string> | |||||
#include <iostream> | |||||
int main() { | |||||
size_t v = 13; | |||||
auto map = new mct20::lfhmap<size_t, std::string, 80000>(); | |||||
for(int a = 0; a < 250000; a++) { | |||||
//if(a % 1000 == 0) std::cout << a << std::endl; | |||||
map->set(v, std::to_string(v)); | |||||
if(auto acc = map->get(v); acc) { | |||||
const std::string& t = acc.value(); | |||||
if(t != std::to_string(v)) | |||||
return 1; | |||||
} else | |||||
return 1; | |||||
v*=121; | |||||
v+=17; | |||||
} | |||||
return 0; | |||||
} |
@ -0,0 +1,22 @@ | |||||
#include "lfhmap.hpp" | |||||
#include <string> | |||||
#include <iostream> | |||||
#include <chrono> | |||||
int main() { | |||||
size_t v = 13; | |||||
auto map = new mct20::lfhmap<size_t, size_t, 80000>(); | |||||
auto start = std::chrono::high_resolution_clock::now(); | |||||
for(int a = 0; a < 250000; a++) { | |||||
map->set(v, v); | |||||
const size_t& t = map->get(v).value(); | |||||
if(t != v) | |||||
return 1; | |||||
v*=121; | |||||
v+=17; | |||||
} | |||||
auto time = std::chrono::high_resolution_clock::now() - start; | |||||
std::cout << "Test 02 took " << std::chrono::duration_cast<std::chrono::milliseconds>(time).count() << "ms" << std::endl; | |||||
std::cout << "Per 1R1W " << std::chrono::duration_cast<std::chrono::nanoseconds>(time).count()/250000 << "ns" << std::endl; | |||||
return 0; | |||||
} |
@ -0,0 +1,30 @@ | |||||
#include "lfhmap.hpp" | |||||
#include <string> | |||||
#include <iostream> | |||||
#include <chrono> | |||||
int main() { | |||||
size_t v = 13; | |||||
auto map = new mct20::lfhmap<size_t, size_t, 80000>(); | |||||
for(int a = 0; a < 250000; a++) { | |||||
map->set(v, v); | |||||
const size_t& t = map->get(v).value(); | |||||
if(t != v) | |||||
return 1; | |||||
v*=121; | |||||
v+=17; | |||||
} | |||||
v = 13; | |||||
auto start = std::chrono::high_resolution_clock::now(); | |||||
for(int a = 0; a < 250000; a++) { | |||||
const size_t& t = map->get(v).value(); | |||||
if(t != v) | |||||
return 1; | |||||
v*=121; | |||||
v+=17; | |||||
} | |||||
auto time = std::chrono::high_resolution_clock::now() - start; | |||||
std::cout << "Test 03 took " << std::chrono::duration_cast<std::chrono::milliseconds>(time).count() << "ms" << std::endl; | |||||
std::cout << "Per 1R " << std::chrono::duration_cast<std::chrono::nanoseconds>(time).count()/250000 << "ns" << std::endl; | |||||
return 0; | |||||
} |
@ -0,0 +1,42 @@ | |||||
#include "lfhmap.hpp" | |||||
#include <string> | |||||
#include <iostream> | |||||
#include <future> | |||||
#include <vector> | |||||
template<typename fn> | |||||
void repeat(size_t nb, fn v) { | |||||
while(nb--) { | |||||
v(); | |||||
} | |||||
} | |||||
int main() { | |||||
constexpr size_t thread_cnt = 16; | |||||
size_t v = 0; | |||||
auto map = new mct20::lfhmap<size_t, std::string, 80000>(); | |||||
std::vector<std::future<int>> finals; | |||||
repeat(thread_cnt, [&](){ | |||||
size_t v2 = v; | |||||
v++; | |||||
finals.push_back(std::async(std::launch::async, [&map, v2](){ | |||||
for(int a = v2; a < 250000; a+=thread_cnt) { | |||||
map->set(a, std::to_string(a)); | |||||
if(auto acc = map->get(a); acc) { | |||||
const std::string& t = acc.value(); | |||||
if(t != std::to_string(a)) | |||||
return 1; | |||||
} else | |||||
return 1; | |||||
} | |||||
return 0; | |||||
})); | |||||
}); | |||||
for(auto& a : finals) a.wait(); | |||||
int ret = 0; | |||||
for(auto& a : finals) ret += a.get(); | |||||
return ret; | |||||
} |