Browse Source

push lock free hash table

master
Ludovic 'Archivist' Lagouardette 3 years ago
parent
commit
6cdb667fa4
9 changed files with 349 additions and 0 deletions
  1. +1
    -0
      .gitignore
  2. +16
    -0
      .vscode/launch.json
  3. +6
    -0
      .vscode/settings.json
  4. +198
    -0
      include/lfhmap.hpp
  5. +13
    -0
      tests.sh
  6. +21
    -0
      tests/test01.cpp
  7. +22
    -0
      tests/test02.cpp
  8. +30
    -0
      tests/test03.cpp
  9. +42
    -0
      tests/test04.cpp

+ 1
- 0
.gitignore View File

@ -0,0 +1 @@
a.out

+ 16
- 0
.vscode/launch.json View File

@ -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"
}
]
}

+ 6
- 0
.vscode/settings.json View File

@ -0,0 +1,6 @@
{
"clang.cxxflags": [
"-std=c++17",
"-I${workspaceRoot}/include"
]
}

+ 198
- 0
include/lfhmap.hpp View File

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

+ 13
- 0
tests.sh View File

@ -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"

+ 21
- 0
tests/test01.cpp View File

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

+ 22
- 0
tests/test02.cpp View File

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

+ 30
- 0
tests/test03.cpp View File

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

+ 42
- 0
tests/test04.cpp View File

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

Loading…
Cancel
Save