Skip to content

Commit 8aec3f8

Browse files
Charlie FraschCharlie Frasch
Charlie Frasch
authored and
Charlie Frasch
committed
WIP
1 parent 5117641 commit 8aec3f8

11 files changed

+410
-12
lines changed

CMakeLists.txt

+47-8
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,61 @@ set(CMAKE_CXX_COMPILER /usr/bin/g++-12)
44

55
project(cppcon2023)
66

7+
cmake_path(GET PROJECT_BINARY_DIR STEM buildDir)
8+
string(TOLOWER ${buildDir} buildDir)
9+
if(buildDir STREQUAL "release")
10+
set(buildType RelWithDebInfo)
11+
elseif(buildDir STREQUAL "debug")
12+
set(buildType Debug)
13+
else()
14+
set(buildType Debug)
15+
endif()
716

8-
# TODO fix this
9-
set(CMAKE_BUILD_TYPE Debug)
17+
if(NOT CMAKE_BUILD_TYPE)
18+
set(CMAKE_BUILD_TYPE ${buildType})
19+
endif()
1020

11-
set(CMAKE_CXX_STANDARD 20)
21+
# if(CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo OR CMAKE_BUILD_TYPE STREQUAL Release )
22+
# endif()
23+
24+
set(CMAKE_CXX_STANDARD 23)
1225
set(CMAKE_CXX_STANDARD_REQUIRED true)
1326
set(CMAKE_CXX_EXTENSIONS OFF)
1427
add_compile_options(-Wall -Werror -Wextra -Wconversion)
1528
add_compile_options(-Wno-unused-parameter)
1629

17-
add_executable(benchmark benchmark.cpp)
18-
target_link_libraries(benchmark pthread)
1930

20-
# add_executable(benchmark.tsan benchmark.cpp)
21-
# target_compile_options(benchmark.tsan -fsanitize=thread)
22-
# target_link_libraries(benchmark.tsan pthread)
31+
add_executable(fifo1 fifo1.cpp)
32+
target_link_libraries(fifo1 pthread)
33+
34+
add_executable(fifo1.tsan fifo1.cpp)
35+
target_compile_options(fifo1.tsan PRIVATE -fsanitize=thread)
36+
target_link_libraries(fifo1.tsan PRIVATE pthread tsan)
37+
38+
39+
add_executable(fifo2 fifo2.cpp)
40+
target_link_libraries(fifo2 pthread)
41+
42+
add_executable(fifo2.tsan fifo2.cpp)
43+
target_compile_options(fifo2.tsan PRIVATE -fsanitize=thread)
44+
target_link_libraries(fifo2.tsan PRIVATE pthread tsan)
45+
46+
47+
add_executable(fifo3 fifo3.cpp)
48+
target_link_libraries(fifo3 pthread)
49+
50+
add_executable(fifo3.tsan fifo3.cpp)
51+
target_compile_options(fifo3.tsan PRIVATE -fsanitize=thread)
52+
target_link_libraries(fifo3.tsan PRIVATE pthread tsan)
53+
54+
55+
add_executable(fifo4 fifo4.cpp)
56+
target_link_libraries(fifo4 pthread)
57+
58+
add_executable(fifo4.tsan fifo4.cpp)
59+
target_compile_options(fifo4.tsan PRIVATE -fsanitize=thread)
60+
target_link_libraries(fifo4.tsan PRIVATE pthread tsan)
61+
2362

2463
include(GoogleTest)
2564
enable_testing()

Fifo1.hpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class Fifo1
2727
if (full()) {
2828
return false;
2929
}
30-
new (ring_.get() + pushCursor_ % size_) T(value);
30+
new (&ring_[pushCursor_ % size_]) T(value);
3131
++pushCursor_;
3232

3333
return true;
@@ -39,8 +39,8 @@ class Fifo1
3939
if (empty()) {
4040
return false;
4141
}
42-
value = ring_.get()[popCursor_ % size_];
43-
ring_.get()[popCursor_ % size_].~T();
42+
value = ring_[popCursor_ % size_];
43+
ring_[popCursor_ % size_].~T();
4444
++popCursor_;
4545

4646
return true;
@@ -49,7 +49,7 @@ class Fifo1
4949
private:
5050
std::size_t size_;
5151

52-
using RingType = std::unique_ptr<ValueType, decltype(&std::free)>;
52+
using RingType = std::unique_ptr<ValueType[], decltype(&std::free)>;
5353
RingType ring_;
5454

5555
std::size_t pushCursor_{};

Fifo1_ut.cpp

+34
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,37 @@ TEST(Fifo1, pop) {
5757
}
5858
EXPECT_FALSE(fifo.pop(value));
5959
}
60+
61+
TEST(Fifo1, popFullFifo) {
62+
auto fifo = TestFifo(4);
63+
64+
auto value = TestFifo::ValueType{};
65+
EXPECT_FALSE(fifo.pop(value));
66+
67+
for (auto i = 0u; i < fifo.size(); ++i) {
68+
fifo.push(42 + i);
69+
}
70+
EXPECT_TRUE(fifo.full());
71+
72+
for (auto i = 0u; i < fifo.size()*4; ++i) {
73+
EXPECT_TRUE(fifo.pop(value));
74+
EXPECT_EQ(42+i, value);
75+
76+
EXPECT_TRUE(fifo.push(42 + 4 + i));
77+
EXPECT_TRUE(fifo.full());
78+
}
79+
}
80+
81+
TEST(Fifo1, popEmpty) {
82+
auto fifo = TestFifo(4);
83+
84+
auto value = TestFifo::ValueType{};
85+
EXPECT_FALSE(fifo.pop(value));
86+
87+
for (auto i = 0u; i < fifo.size()*4; ++i) {
88+
EXPECT_TRUE(fifo.empty());
89+
EXPECT_TRUE(fifo.push(42 + i));
90+
EXPECT_TRUE(fifo.pop(value));
91+
EXPECT_EQ(42+i, value);
92+
}
93+
}

Fifo2.hpp

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#pragma once
2+
3+
#include <atomic>
4+
#include <cstdlib>
5+
#include <memory>
6+
7+
8+
/// Threadsafe but flawed circular FIFO
9+
template<typename T>
10+
class Fifo2
11+
{
12+
public:
13+
using ValueType = T;
14+
15+
explicit Fifo2(std::size_t size)
16+
: size_{size}
17+
, ring_{
18+
static_cast<ValueType*>(std::aligned_alloc(alignof(T), size * sizeof(T))),
19+
&std::free}
20+
{}
21+
22+
23+
std::size_t size() const { return size_; }
24+
bool empty() const { return popCursor_ == pushCursor_; }
25+
bool full() const { return (pushCursor_ - popCursor_) == size_; }
26+
27+
bool push(T const& value) {
28+
if (full()) {
29+
return false;
30+
}
31+
new (&ring_[pushCursor_ % size_]) T(value);
32+
++pushCursor_;
33+
34+
return true;
35+
}
36+
37+
/// 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) {
40+
if (empty()) {
41+
return false;
42+
}
43+
value = ring_[popCursor_ % size_];
44+
ring_[popCursor_ % size_].~T();
45+
++popCursor_;
46+
47+
return true;
48+
}
49+
50+
private:
51+
std::size_t size_;
52+
53+
using RingType = std::unique_ptr<ValueType[], decltype(&std::free)>;
54+
RingType ring_;
55+
56+
/// Loaded and stored by the push thread; loaded by the pop thread
57+
std::atomic<std::size_t> pushCursor_{};
58+
59+
/// Loaded and stored by the pop thread; loaded by the push thread
60+
std::atomic<std::size_t> popCursor_{};
61+
};

Fifo3.hpp

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#pragma once
2+
3+
#include <atomic>
4+
#include <cstdlib>
5+
#include <memory>
6+
#include <new>
7+
8+
9+
/// Threadsafe, efficient circular FIFO
10+
template<typename T>
11+
class Fifo3
12+
{
13+
public:
14+
using ValueType = T;
15+
16+
explicit Fifo3(std::size_t size)
17+
: size_{size}
18+
, ring_{
19+
static_cast<ValueType*>(std::aligned_alloc(alignof(T), size * sizeof(T))),
20+
&std::free}
21+
{}
22+
23+
24+
std::size_t size() const { return size_; }
25+
bool empty() const { return popCursor_ == pushCursor_; }
26+
bool full() const { return (pushCursor_ - popCursor_) == size_; }
27+
28+
bool push(T const& value) {
29+
auto pushCursor = pushCursor_.load(std::memory_order_relaxed);
30+
auto popCursor = popCursor_.load(std::memory_order_acquire);
31+
if (full(pushCursor, popCursor)) {
32+
return false;
33+
}
34+
new (&ring_[pushCursor % size_]) T(value);
35+
pushCursor_.store(pushCursor + 1, std::memory_order_release);
36+
return true;
37+
}
38+
39+
/// 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) {
42+
auto pushCursor = pushCursor_.load(std::memory_order_acquire);
43+
auto popCursor = popCursor_.load(std::memory_order_relaxed);
44+
if (empty(pushCursor, popCursor)) {
45+
return false;
46+
}
47+
value = ring_[popCursor % size_];
48+
ring_[popCursor % size_].~T();
49+
popCursor_.store(popCursor + 1, std::memory_order_release);
50+
return true;
51+
}
52+
53+
private:
54+
bool full(std::size_t pushCursor, std::size_t popCursor) const {
55+
return (pushCursor - popCursor) == size_;
56+
}
57+
static bool empty(std::size_t pushCursor, std::size_t popCursor) {
58+
return pushCursor == popCursor;
59+
}
60+
61+
private:
62+
std::size_t size_;
63+
64+
using RingType = std::unique_ptr<ValueType[], decltype(&std::free)>;
65+
RingType ring_;
66+
67+
// https://stackoverflow.com/questions/39680206/understanding-stdhardware-destructive-interference-size-and-stdhardware-cons
68+
static constexpr auto hardware_destructive_interference_size = std::size_t{128};
69+
70+
/// Loaded and stored by the push thread; loaded by the pop thread
71+
alignas(hardware_destructive_interference_size) std::atomic<std::size_t> pushCursor_{};
72+
73+
/// Loaded and stored by the pop thread; loaded by the push thread
74+
alignas(hardware_destructive_interference_size) std::atomic<std::size_t> popCursor_{};
75+
};

Fifo4.hpp

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#pragma once
2+
3+
#include <atomic>
4+
#include <cstdlib>
5+
#include <memory>
6+
#include <new>
7+
8+
9+
/// Threadsafe, efficient circular FIFO
10+
template<typename T>
11+
class Fifo4
12+
{
13+
public:
14+
using ValueType = T;
15+
16+
explicit Fifo4(std::size_t size)
17+
: size_{size}
18+
, ring_{
19+
static_cast<ValueType*>(std::aligned_alloc(alignof(T), size * sizeof(T))),
20+
&std::free}
21+
{}
22+
23+
24+
std::size_t size() const { return size_; }
25+
bool empty() const { return popCursor_ == pushCursor_; }
26+
bool full() const { return (pushCursor_ - popCursor_) == size_; }
27+
28+
bool push(T const& value) {
29+
auto pushCursor = pushCursor_.load(std::memory_order_relaxed);
30+
if (full(pushCursor, popCursorCached_)) {
31+
popCursorCached_ = popCursor_.load(std::memory_order_acquire);
32+
}
33+
if (full(pushCursor, popCursorCached_)) {
34+
return false;
35+
}
36+
37+
new (&ring_[pushCursor % size_]) T(value);
38+
pushCursor_.store(pushCursor + 1, std::memory_order_release);
39+
return true;
40+
}
41+
42+
/// Pop one object from the fifo.
43+
/// @return `true` if the pop operation is successful; `false` if fifo was empty.
44+
bool pop(T& value) {
45+
auto popCursor = popCursor_.load(std::memory_order_relaxed);
46+
if (empty(pushCursorCached_, popCursor)) {
47+
pushCursorCached_ = pushCursor_.load(std::memory_order_acquire);
48+
}
49+
if (empty(pushCursorCached_, popCursor)) {
50+
return false;
51+
}
52+
53+
value = ring_[popCursor % size_];
54+
ring_[popCursor % size_].~T();
55+
popCursor_.store(popCursor + 1, std::memory_order_release);
56+
return true;
57+
}
58+
59+
private:
60+
bool full(std::size_t pushCursor, std::size_t popCursor) const {
61+
return (pushCursor - popCursor) == size_;
62+
}
63+
static bool empty(std::size_t pushCursor, std::size_t popCursor) {
64+
return pushCursor == popCursor;
65+
}
66+
67+
private:
68+
std::size_t size_;
69+
70+
using RingType = std::unique_ptr<ValueType[], decltype(&std::free)>;
71+
RingType ring_;
72+
73+
// https://stackoverflow.com/questions/39680206/understanding-stdhardware-destructive-interference-size-and-stdhardware-cons
74+
static constexpr auto hardware_destructive_interference_size = std::size_t{128};
75+
76+
/// Loaded and stored by the push thread; loaded by the pop thread
77+
// TODO fix initialization on the others
78+
alignas(hardware_destructive_interference_size) std::atomic<std::size_t> pushCursor_;
79+
80+
/// Exclusive to the pop thread
81+
alignas(hardware_destructive_interference_size) std::size_t popCursorCached_{};
82+
83+
/// Loaded and stored by the pop thread; loaded by the push thread
84+
alignas(hardware_destructive_interference_size) std::atomic<std::size_t> popCursor_;
85+
86+
/// Exclusive to the push thread
87+
alignas(hardware_destructive_interference_size) std::size_t pushCursorCached_{};
88+
};

0 commit comments

Comments
 (0)