implemented thread safe bitblock structure for bitsets
This commit is contained in:
parent
d38aa4a38b
commit
742b5a64c4
79
data_structures/bitset/bitblock.hpp
Normal file
79
data_structures/bitset/bitblock.hpp
Normal file
@ -0,0 +1,79 @@
|
||||
#ifndef MEMGRAPH_DATA_STRUCTURES_BITBLOCK_HPP
|
||||
#define MEMGRAPH_DATA_STRUCTURES_BITBLOCK_HPP
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <atomic>
|
||||
#include <unistd.h>
|
||||
|
||||
template<class block_t = uint8_t,
|
||||
size_t N = 1>
|
||||
struct BitBlock
|
||||
{
|
||||
BitBlock() : block(0) {}
|
||||
|
||||
static constexpr size_t bits = sizeof(block_t) * 8;
|
||||
static constexpr size_t size = bits / N;
|
||||
|
||||
// e.g. if N = 2,
|
||||
// mask = 11111111 >> 6 = 00000011
|
||||
static constexpr block_t mask = (block_t)(-1) >> (bits - N);
|
||||
|
||||
uint8_t at(size_t n)
|
||||
{
|
||||
assert(n < size);
|
||||
|
||||
block_t b = block.load(std::memory_order_relaxed);
|
||||
return (b >> n * N) & mask;
|
||||
}
|
||||
|
||||
// caution! this method assumes that the value on the sub-block n is 0..0!
|
||||
void set(size_t n, block_t value)
|
||||
{
|
||||
assert(n < size);
|
||||
assert(value < (1UL << N));
|
||||
block_t b, new_value;
|
||||
|
||||
while(true)
|
||||
{
|
||||
b = block.load(std::memory_order_relaxed);
|
||||
new_value = b | (value << n * N);
|
||||
|
||||
if(block.compare_exchange_weak(b, new_value,
|
||||
std::memory_order_release,
|
||||
std::memory_order_relaxed))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// reduces contention and works better than pure while
|
||||
usleep(250);
|
||||
}
|
||||
}
|
||||
|
||||
void clear(size_t n)
|
||||
{
|
||||
assert(n < size);
|
||||
block_t b, new_value;
|
||||
|
||||
while(true)
|
||||
{
|
||||
b = block.load(std::memory_order_relaxed);
|
||||
new_value = b & ~(mask << n * N);
|
||||
|
||||
if(block.compare_exchange_weak(b, new_value,
|
||||
std::memory_order_release,
|
||||
std::memory_order_relaxed))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// reduces contention and works better than pure while
|
||||
usleep(250);
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<block_t> block;
|
||||
};
|
||||
|
||||
#endif
|
186
test/bitblock.cpp
Normal file
186
test/bitblock.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include <thread>
|
||||
|
||||
#include "catch.hpp"
|
||||
#include "data_structures/bitset/bitblock.hpp"
|
||||
#include "sync/spinlock.hpp"
|
||||
|
||||
TEST_CASE("BitBlock should be empty on construction")
|
||||
{
|
||||
BitBlock<> bb;
|
||||
REQUIRE(bb.block.load(std::memory_order_relaxed) == 0);
|
||||
}
|
||||
|
||||
template <class block_t, size_t N>
|
||||
void test_sizes()
|
||||
{
|
||||
BitBlock<block_t, N> bb;
|
||||
auto bits = bb.bits;
|
||||
auto size = bb.size;
|
||||
|
||||
REQUIRE(bits == (8 * sizeof(block_t)));
|
||||
REQUIRE(size == (8 * sizeof(block_t) / N));
|
||||
}
|
||||
|
||||
template <class T, size_t N>
|
||||
struct test_sizes_loop : test_sizes_loop<T, N - 1>
|
||||
{
|
||||
test_sizes_loop()
|
||||
{
|
||||
test_sizes<T, N>();
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct test_sizes_loop<T, 1>
|
||||
{
|
||||
test_sizes_loop()
|
||||
{
|
||||
test_sizes<T, 1>();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("BitBlock bit sizes should be set correctly")
|
||||
{
|
||||
test_sizes_loop<uint8_t, 8> size_test8;
|
||||
test_sizes_loop<uint16_t, 8> size_test16;
|
||||
test_sizes_loop<uint32_t, 8> size_test32;
|
||||
test_sizes_loop<uint64_t, 8> size_test64;
|
||||
}
|
||||
|
||||
TEST_CASE("Values load correctly from the BitBlock")
|
||||
{
|
||||
constexpr uint32_t k = 0xCA3F;
|
||||
|
||||
SECTION("Block size = 1")
|
||||
{
|
||||
constexpr size_t N = 1;
|
||||
|
||||
BitBlock<uint32_t, N> bb;
|
||||
bb.block.store(k);
|
||||
|
||||
for(size_t i = 0; i < bb.size; ++i)
|
||||
REQUIRE(bb.at(i) == ((k >> i) & 1));
|
||||
}
|
||||
|
||||
SECTION("Block size = 4")
|
||||
{
|
||||
constexpr size_t N = 4;
|
||||
|
||||
BitBlock<uint32_t, N> bb;
|
||||
bb.block.store(k);
|
||||
|
||||
for(size_t i = 0; i < bb.size; ++i)
|
||||
REQUIRE(bb.at(i) == ((k >> (N * i)) & 0xF));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("You can set a bit in a BitBlock and get the result")
|
||||
{
|
||||
SECTION("Block size = 1")
|
||||
{
|
||||
BitBlock<uint8_t, 1> bb;
|
||||
|
||||
for(size_t i = 0; i < bb.bits; ++i)
|
||||
{
|
||||
bb.set(i, 1);
|
||||
|
||||
for(size_t j = 0; j < bb.bits; ++j)
|
||||
{
|
||||
if(j <= i)
|
||||
REQUIRE(bb.at(j) == 1);
|
||||
else
|
||||
REQUIRE(bb.at(j) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Block size = 2")
|
||||
{
|
||||
constexpr size_t N = 2;
|
||||
|
||||
BitBlock<uint16_t, N> bb;
|
||||
|
||||
for(size_t i = 0; i < bb.size; ++i)
|
||||
{
|
||||
auto k = i % (1 << N);
|
||||
bb.set(i, k);
|
||||
|
||||
for(size_t j = 0; j < bb.size; j++)
|
||||
{
|
||||
auto l = j % (1 << N);
|
||||
|
||||
if(j <= i)
|
||||
REQUIRE(bb.at(j) == l);
|
||||
else
|
||||
REQUIRE(bb.at(j) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Block size = 5")
|
||||
{
|
||||
constexpr size_t N = 5;
|
||||
|
||||
BitBlock<uint16_t, N> bb;
|
||||
|
||||
for(size_t i = 0; i < bb.size; ++i)
|
||||
{
|
||||
auto k = i % (1 << N);
|
||||
bb.set(i, k);
|
||||
|
||||
for(size_t j = 0; j < bb.size; j++)
|
||||
{
|
||||
auto l = j % (1 << N);
|
||||
|
||||
if(j <= i)
|
||||
REQUIRE(bb.at(j) == l);
|
||||
else
|
||||
REQUIRE(bb.at(j) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, size_t N>
|
||||
void bitblock_thead_test(BitBlock<T, N>* bb, size_t idx, size_t n)
|
||||
{
|
||||
static SpinLock lock;
|
||||
uint8_t x;
|
||||
|
||||
uint64_t sum = 0, actual = 0;
|
||||
|
||||
for(size_t i = 0; i < n; ++i)
|
||||
{
|
||||
int y = i % 2;
|
||||
actual += i * y;
|
||||
|
||||
bb->set(idx, y);
|
||||
x = bb->at(idx);
|
||||
|
||||
sum += i * x;
|
||||
|
||||
bb->clear(idx);
|
||||
x = bb->at(idx);
|
||||
}
|
||||
|
||||
auto guard = std::unique_lock<SpinLock>(lock);
|
||||
REQUIRE(sum == actual);
|
||||
}
|
||||
|
||||
TEST_CASE("(try to) Test multithreaded correctness")
|
||||
{
|
||||
BitBlock<uint64_t, 1> bb;
|
||||
|
||||
constexpr int N = 2;
|
||||
constexpr int K = 500000;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
for(int i = 0; i < N; ++i)
|
||||
threads.push_back(std::thread(
|
||||
bitblock_thead_test<uint64_t, 1>, &bb, i, K));
|
||||
|
||||
for(auto& thread : threads){
|
||||
thread.join();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user