@ -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; | |||
} |