Skip to content

Commit 7e7a4ee

Browse files
committed
More efficient bit claiming
1 parent f870186 commit 7e7a4ee

File tree

3 files changed

+57
-38
lines changed

3 files changed

+57
-38
lines changed

relaxed_concurrent_fifo/atomic_bitset.h

+37-36
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@
77
#include <atomic>
88
#include <cassert>
99

10-
template <size_t BIT_COUNT>
11-
struct min_fit_int {
12-
using type = std::conditional_t<BIT_COUNT <= 8, uint8_t,
13-
std::conditional_t<BIT_COUNT <= 16, uint16_t,
14-
std::conditional_t<BIT_COUNT <= 32, uint32_t,
15-
std::conditional_t<BIT_COUNT <= 64, uint64_t,
16-
void>>>>;
17-
};
18-
1910
template <bool SET, typename T>
2011
constexpr bool set_bit_atomic(std::atomic<T>& data, size_t index) {
2112
T old_val;
@@ -37,12 +28,28 @@ constexpr bool set_bit_atomic(std::atomic<T>& data, size_t index) {
3728
template <size_t N>
3829
class atomic_bitset {
3930
private:
40-
using internal_type = typename min_fit_int<N>::type;
31+
std::array<std::atomic<uint64_t>, N / 64 + (N % 64 ? 1 : 0)> data;
4132

42-
// TODO: Is it preferable to always have an array of bytes since it gives greater granularity to the atomics?
43-
std::conditional_t<N <= 64, std::atomic<internal_type>, std::array<std::atomic<uint8_t>, N / 8 + (N % 8 ? 1 : 0)>> data;
33+
static inline thread_local std::random_device dev;
34+
static inline thread_local std::minstd_rand rng{ dev() };
35+
static inline thread_local std::uniform_int_distribution dist{ 0, static_cast<int>(N - 1) };
36+
37+
template <bool IS_SET>
38+
static constexpr size_t claim_bit_singular(std::atomic<uint64_t>& data) {
39+
auto initial_rot = dist(rng);
40+
auto rotated = std::rotr<uint64_t>(data, initial_rot);
41+
int counted;
42+
for (int i = 0; i < 64; i += counted) {
43+
counted = IS_SET ? std::countr_zero(rotated) : std::countr_one(rotated);
44+
auto original_index = (initial_rot + i + counted) % 64;
45+
if (set_bit_atomic<!IS_SET>(data, original_index)) {
46+
return original_index;
47+
}
48+
rotated >>= ++counted;
49+
}
4450

45-
static constexpr bool uses_array = N > 64;
51+
return std::numeric_limits<size_t>::max();
52+
}
4653

4754
public:
4855
static constexpr size_t size() { return N; }
@@ -54,11 +61,7 @@ class atomic_bitset {
5461
/// <returns>Whether the bit has been newly set. false means the bit had already been set.</returns>
5562
constexpr bool set(size_t index) {
5663
assert(index < size());
57-
if constexpr (uses_array) {
58-
return set_bit_atomic<true>(data[index / 8], index % 8);
59-
} else if constexpr (!uses_array) {
60-
return set_bit_atomic<true>(data, index);
61-
}
64+
return set_bit_atomic<true>(data[index / 64], index % 64);
6265
}
6366

6467
/// <summary>
@@ -68,37 +71,35 @@ class atomic_bitset {
6871
/// <returns>Whether the bit has been newly set. false means the bit had already been set.</returns>
6972
constexpr bool reset(size_t index) {
7073
assert(index < size());
71-
if constexpr (uses_array) {
72-
return set_bit_atomic<false>(data[index / 8], index % 8);
73-
} else if constexpr (!uses_array) {
74-
return set_bit_atomic<false>(data, index);
75-
}
74+
return set_bit_atomic<false>(data[index / 64], index % 64);
7675
}
7776

7877
constexpr bool test(size_t index, std::memory_order order = std::memory_order_seq_cst) const {
7978
assert(index < size());
80-
if constexpr (uses_array) {
81-
return data[index / 8].load(order) & (1 << (index % 8));
82-
} else if constexpr (!uses_array) {
83-
return data.load(order) & (1ull << index);
84-
}
79+
return data[index / 64].load(order) & (1ull << (index % 64));
8580
}
8681

8782
constexpr bool operator[](size_t index) const {
8883
return test(index);
8984
}
9085

9186
constexpr bool any() const {
92-
if constexpr (uses_array) {
93-
for (auto& elem : data) {
94-
if (elem) {
95-
return true;
96-
}
87+
for (auto& elem : data) {
88+
if (elem) {
89+
return true;
90+
}
91+
}
92+
return false;
93+
}
94+
95+
template <bool IS_SET>
96+
constexpr size_t claim_bit() {
97+
for (size_t i = 0; i < data.size(); i++) {
98+
if (auto ret = claim_bit_singular<IS_SET>(data[i]); ret != std::numeric_limits<size_t>::max()) {
99+
return ret + i * 64;
97100
}
98-
return false;
99-
} else {
100-
return data;
101101
}
102+
return std::numeric_limits<size_t>::max();
102103
}
103104
};
104105

relaxed_concurrent_fifo/main.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,24 @@ void test_consistency(double prefill) {
126126
}
127127
}
128128

129+
void test_continuous_bitset_claim() {
130+
auto gen = std::bind(std::uniform_int_distribution<>(0, 1), std::default_random_engine());
131+
while (true) {
132+
atomic_bitset<128> a;
133+
std::vector<bool> b(128);
134+
for (int i = 0; i < 128; i++) {
135+
if (gen()) {
136+
a.set(i);
137+
b[i] = true;
138+
}
139+
}
140+
auto result = a.claim_bit<true>();
141+
if (a[result] || !b[result]) {
142+
throw std::runtime_error("Incorrect!");
143+
}
144+
}
145+
}
146+
129147
template <typename BENCHMARK>
130148
void run_benchmark(const std::string& test_name, const std::vector<std::unique_ptr<benchmark_provider<BENCHMARK>>>& instances, const std::vector<double>& prefill_amounts,
131149
const std::vector<size_t>& processor_counts, int test_iterations, int test_time_seconds) {

relaxed_concurrent_fifo/relaxed_fifo.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class relaxed_fifo {
150150
friend relaxed_fifo;
151151

152152
std::random_device dev;
153-
std::mt19937 rng{dev()};
153+
std::minstd_rand rng{dev()};
154154
// TODO: Check template parameter here.
155155
std::uniform_int_distribution<size_t> dist{0, BLOCKS_PER_WINDOW - 1};
156156

@@ -416,7 +416,7 @@ class relaxed_fifo {
416416
uint64_t window_index = fifo.write_window;
417417
window_t& window = fifo.buffer[window_index % fifo.window_count];
418418

419-
auto free_bit = claim_free_bit<true>(window.occupied_set);
419+
auto free_bit = window.occupied_set.template claim_bit<true>();
420420
if (free_bit == std::numeric_limits<size_t>::max()) {
421421
if (window_index + 1 - fifo.read_window == fifo.window_count) {
422422
// TODO: Maybe consider if the write window already moved?

0 commit comments

Comments
 (0)