Skip to content

Commit fc33980

Browse files
Charlie FraschCharlie Frasch
Charlie Frasch
authored and
Charlie Frasch
committed
WIP
1 parent a395ce8 commit fc33980

13 files changed

+866
-221
lines changed

CMakeLists.txt

+21-39
Original file line numberDiff line numberDiff line change
@@ -17,52 +17,34 @@ if(NOT CMAKE_BUILD_TYPE)
1717
set(CMAKE_BUILD_TYPE ${buildType})
1818
endif()
1919

20+
set(BOOST_ROOT /mnt/c/Users/Charles/AppData/Local/lxss/rootfs/usr/)
21+
find_package(Boost REQUIRED)
2022

2123
set(CMAKE_CXX_STANDARD 23)
2224
set(CMAKE_CXX_STANDARD_REQUIRED true)
2325
set(CMAKE_CXX_EXTENSIONS OFF)
2426
add_compile_options(-Wall -Werror -Wextra -Wconversion)
2527
add_compile_options(-Wno-unused-parameter)
2628

27-
28-
add_executable(fifo1 fifo1.cpp)
29-
target_link_libraries(fifo1 pthread)
30-
31-
add_executable(fifo1.tsan fifo1.cpp)
32-
target_compile_options(fifo1.tsan PRIVATE -fsanitize=thread)
33-
target_link_libraries(fifo1.tsan PRIVATE pthread tsan)
34-
35-
36-
add_executable(fifo2 fifo2.cpp)
37-
target_link_libraries(fifo2 pthread)
38-
39-
add_executable(fifo2.tsan fifo2.cpp)
40-
target_compile_options(fifo2.tsan PRIVATE -fsanitize=thread)
41-
target_link_libraries(fifo2.tsan PRIVATE pthread tsan)
42-
43-
44-
add_executable(fifo3 fifo3.cpp)
45-
target_link_libraries(fifo3 pthread)
46-
47-
add_executable(fifo3.tsan fifo3.cpp)
48-
target_compile_options(fifo3.tsan PRIVATE -fsanitize=thread)
49-
target_link_libraries(fifo3.tsan PRIVATE pthread tsan)
50-
51-
52-
add_executable(fifo4 fifo4.cpp)
53-
target_link_libraries(fifo4 pthread)
54-
55-
add_executable(fifo4.tsan fifo4.cpp)
56-
target_compile_options(fifo4.tsan PRIVATE -fsanitize=thread)
57-
target_link_libraries(fifo4.tsan PRIVATE pthread tsan)
58-
59-
60-
# add_executable(fifo7 fifo7.cpp)
61-
# target_link_libraries(fifo7 pthread)
62-
#
63-
# add_executable(fifo7.tsan fifo7.cpp)
64-
# target_compile_options(fifo7.tsan PRIVATE -fsanitize=thread)
65-
# target_link_libraries(fifo7.tsan PRIVATE pthread tsan)
29+
function(add_fifo fifo)
30+
add_executable(${fifo} ${CMAKE_SOURCE_DIR}/${fifo}.cpp)
31+
target_link_libraries(${fifo} pthread)
32+
33+
add_executable(${fifo}.tsan ${CMAKE_SOURCE_DIR}/${fifo}.cpp)
34+
target_compile_options(${fifo}.tsan PRIVATE -fsanitize=thread)
35+
target_link_libraries(${fifo}.tsan PRIVATE pthread tsan)
36+
endfunction()
37+
38+
39+
add_fifo(fifo1)
40+
add_fifo(fifo2)
41+
add_fifo(fifo3)
42+
add_fifo(fifo4)
43+
add_fifo(fifo5)
44+
add_fifo(boost_lockfree)
45+
add_fifo(rigtorp)
46+
target_compile_options(rigtorp PRIVATE -Wno-interference-size)
47+
target_compile_options(rigtorp.tsan PRIVATE -Wno-interference-size)
6648

6749

6850
include(GoogleTest)

Fifo1.hpp

+29-20
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,42 @@
44
#include <memory>
55

66

7-
/// Non-threadsafe circular FIFO
8-
template<typename T>
9-
class Fifo1
7+
/// Non-threadsafe circular FIFO; has data races
8+
template<typename T, typename Alloc = std::allocator<T>>
9+
class Fifo1 : private Alloc
1010
{
1111
public:
12-
using ValueType = T;
12+
using value_type = T;
13+
using allocator_traits = std::allocator_traits<Alloc>;
14+
using size_type = typename allocator_traits::size_type;
1315

14-
explicit Fifo1(std::size_t size)
15-
: size_{size}
16-
, ring_{static_cast<ValueType*>(std::aligned_alloc(alignof(T), size * sizeof(T))), &std::free}
16+
explicit Fifo1(size_type size, Alloc const& alloc = Alloc{})
17+
: Alloc{alloc}
18+
, size_{size}
19+
, ring_{allocator_traits::allocate(*this, size)}
1720
{}
1821

22+
// For consistency with other fifos
23+
Fifo1(Fifo1 const&) = delete;
24+
Fifo1& operator=(Fifo1 const&) = delete;
25+
Fifo1(Fifo1&&) = delete;
26+
Fifo1& operator=(Fifo1&&) = delete;
27+
1928
~Fifo1() {
2029
while(not empty()) {
2130
ring_[popCursor_ % size_].~T();
2231
++popCursor_;
2332
}
33+
allocator_traits::deallocate(*this, ring_, size_);
2434
}
2535

26-
std::size_t size() const { return size_; }
27-
bool empty() const { return popCursor_ == pushCursor_; }
28-
bool full() const { return (pushCursor_ - popCursor_) == size_; }
36+
auto size() const { return size_; }
37+
auto empty() const { return popCursor_ == pushCursor_; }
38+
auto full() const { return (pushCursor_ - popCursor_) == size_; }
2939

30-
bool push(T const& value) {
40+
/// Push one object onto the fifo.
41+
/// @return `true` if the operation is successful; `false` if fifo is full.
42+
auto push(T const& value) {
3143
if (full()) {
3244
return false;
3345
}
@@ -37,8 +49,8 @@ class Fifo1
3749
}
3850

3951
/// Pop one object from the fifo.
40-
/// @return `true` if the pop operation is successful; `false` if fifo was empty.
41-
bool pop(T& value) {
52+
/// @return `true` if the pop operation is successful; `false` if fifo is empty.
53+
auto pop(T& value) {
4254
if (empty()) {
4355
return false;
4456
}
@@ -49,11 +61,8 @@ class Fifo1
4961
}
5062

5163
private:
52-
std::size_t size_;
53-
54-
using RingType = std::unique_ptr<ValueType[], decltype(&std::free)>;
55-
RingType ring_;
56-
57-
std::size_t pushCursor_;
58-
std::size_t popCursor_;
64+
size_type size_;
65+
T* ring_;
66+
size_type pushCursor_;
67+
size_type popCursor_;
5968
};

Fifo2.hpp

+31-19
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,39 @@
11
#pragma once
22

33
#include <atomic>
4-
#include <cstdlib>
54
#include <memory>
65

76

87
/// Threadsafe but flawed circular FIFO
9-
template<typename T>
10-
class Fifo2
8+
template<typename T, typename Alloc = std::allocator<T>>
9+
class Fifo2 : private Alloc
1110
{
1211
public:
13-
using ValueType = T;
12+
using value_type = T;
13+
using allocator_traits = std::allocator_traits<Alloc>;
14+
using size_type = typename allocator_traits::size_type;
1415

15-
explicit Fifo2(std::size_t size)
16-
: size_{size}
17-
, ring_{static_cast<ValueType*>(std::aligned_alloc(alignof(T), size * sizeof(T))),
18-
&std::free}
16+
explicit Fifo2(size_type size, Alloc const& alloc = Alloc{})
17+
: Alloc{alloc}
18+
, size_{size}
19+
, ring_{allocator_traits::allocate(*this, size)}
1920
{}
2021

21-
std::size_t size() const { return size_; }
22-
bool empty() const { return popCursor_ == pushCursor_; }
23-
bool full() const { return (pushCursor_ - popCursor_) == size_; }
22+
~Fifo2() {
23+
while(not empty()) {
24+
ring_[popCursor_ % size_].~T();
25+
++popCursor_;
26+
}
27+
allocator_traits::deallocate(*this, ring_, size_);
28+
}
29+
30+
auto size() const { return size_; }
31+
auto empty() const { return popCursor_ == pushCursor_; }
32+
auto full() const { return (pushCursor_ - popCursor_) == size_; }
2433

25-
bool push(T const& value) {
34+
/// Push one object onto the fifo.
35+
/// @return `true` if the operation is successful; `false` if fifo is full.
36+
auto push(T const& value) {
2637
if (full()) {
2738
return false;
2839
}
@@ -32,8 +43,8 @@ class Fifo2
3243
}
3344

3445
/// Pop one object from the fifo.
35-
/// @return `true` if the pop operation is successful; `false` if fifo was empty.
36-
bool pop(T& value) {
46+
/// @return `true` if the pop operation is successful; `false` if fifo is empty.
47+
auto pop(T& value) {
3748
if (empty()) {
3849
return false;
3950
}
@@ -44,14 +55,15 @@ class Fifo2
4455
}
4556

4657
private:
47-
std::size_t size_;
58+
size_type size_;
59+
T* ring_;
4860

49-
using RingType = std::unique_ptr<ValueType[], decltype(&std::free)>;
50-
RingType ring_;
61+
using CursorType = std::atomic<size_type>;
62+
static_assert(CursorType::is_always_lock_free);
5163

5264
/// Loaded and stored by the push thread; loaded by the pop thread
53-
std::atomic<std::size_t> pushCursor_;
65+
CursorType pushCursor_;
5466

5567
/// Loaded and stored by the pop thread; loaded by the push thread
56-
std::atomic<std::size_t> popCursor_;
68+
CursorType popCursor_;
5769
};

Fifo3.hpp

+40-22
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,40 @@
11
#pragma once
22

33
#include <atomic>
4-
#include <cstdlib>
54
#include <memory>
65
#include <new>
76

87

98
/// Threadsafe, efficient circular FIFO
10-
template<typename T>
11-
class Fifo3
9+
template<typename T, typename Alloc = std::allocator<T>>
10+
class Fifo3 : private Alloc
1211
{
1312
public:
14-
using ValueType = T;
13+
using value_type = T;
14+
using allocator_traits = std::allocator_traits<Alloc>;
15+
using size_type = typename allocator_traits::size_type;
1516

16-
explicit Fifo3(std::size_t size)
17-
: size_{size}
18-
, ring_{static_cast<ValueType*>(std::aligned_alloc(alignof(T), size * sizeof(T))),
19-
&std::free}
17+
explicit Fifo3(size_type size, Alloc const& alloc = Alloc{})
18+
: Alloc{alloc}
19+
, size_{size}
20+
, ring_{allocator_traits::allocate(*this, size)}
2021
{}
2122

22-
std::size_t size() const { return size_; }
23-
bool empty() const { return popCursor_ == pushCursor_; }
24-
bool full() const { return (pushCursor_ - popCursor_) == size_; }
23+
~Fifo3() {
24+
while(not empty()) {
25+
ring_[popCursor_ % size_].~T();
26+
++popCursor_;
27+
}
28+
allocator_traits::deallocate(*this, ring_, size_);
29+
}
30+
31+
auto size() const { return size_; }
32+
auto empty() const { return popCursor_ == pushCursor_; }
33+
auto full() const { return (pushCursor_ - popCursor_) == size_; }
2534

26-
bool push(T const& value) {
35+
/// Push one object onto the fifo.
36+
/// @return `true` if the operation is successful; `false` if fifo is full.
37+
auto push(T const& value) {
2738
auto pushCursor = pushCursor_.load(std::memory_order_relaxed);
2839
auto popCursor = popCursor_.load(std::memory_order_acquire);
2940
if (full(pushCursor, popCursor)) {
@@ -35,8 +46,8 @@ class Fifo3
3546
}
3647

3748
/// Pop one object from the fifo.
38-
/// @return `true` if the pop operation is successful; `false` if fifo was empty.
39-
bool pop(T& value) {
49+
/// @return `true` if the pop operation is successful; `false` if fifo is empty.
50+
auto pop(T& value) {
4051
auto pushCursor = pushCursor_.load(std::memory_order_acquire);
4152
auto popCursor = popCursor_.load(std::memory_order_relaxed);
4253
if (empty(pushCursor, popCursor)) {
@@ -49,25 +60,32 @@ class Fifo3
4960
}
5061

5162
private:
52-
bool full(std::size_t pushCursor, std::size_t popCursor) const {
63+
auto full(size_type pushCursor, size_type popCursor) const {
5364
return (pushCursor - popCursor) == size_;
5465
}
55-
static bool empty(std::size_t pushCursor, std::size_t popCursor) {
66+
static auto empty(size_type pushCursor, size_type popCursor) {
5667
return pushCursor == popCursor;
5768
}
5869

5970
private:
60-
std::size_t size_;
71+
size_type size_;
72+
T* ring_;
6173

62-
using RingType = std::unique_ptr<ValueType[], decltype(&std::free)>;
63-
RingType ring_;
74+
using CursorType = std::atomic<size_type>;
75+
static_assert(CursorType::is_always_lock_free);
6476

6577
// https://stackoverflow.com/questions/39680206/understanding-stdhardware-destructive-interference-size-and-stdhardware-cons
66-
static constexpr auto hardware_destructive_interference_size = std::size_t{128};
78+
// And this:
79+
// error: use of ‘std::hardware_destructive_interference_size’ [-Werror=interference-size]
80+
// note: its value can vary between compiler versions or with different ‘-mtune’ or ‘-mcpu’ flags
81+
// note: if this use is part of a public ABI, change it to instead use a constant variable you define
82+
// note: the default value for the current CPU tuning is 64 bytes
83+
// note: you can stabilize this value with ‘--param hardware_destructive_interference_size=64’, or disable this warning with ‘-Wno-interference-size’
84+
static constexpr auto hardware_destructive_interference_size = size_type{128};
6785

6886
/// Loaded and stored by the push thread; loaded by the pop thread
69-
alignas(hardware_destructive_interference_size) std::atomic<std::size_t> pushCursor_;
87+
alignas(hardware_destructive_interference_size) CursorType pushCursor_;
7088

7189
/// Loaded and stored by the pop thread; loaded by the push thread
72-
alignas(hardware_destructive_interference_size) std::atomic<std::size_t> popCursor_;
90+
alignas(hardware_destructive_interference_size) CursorType popCursor_;
7391
};

0 commit comments

Comments
 (0)