Skip to content

Commit 4a9c329

Browse files
Charlie FraschCharlie Frasch
Charlie Frasch
authored and
Charlie Frasch
committed
Distinguish between size and capacity
1 parent 8ea1fc3 commit 4a9c329

File tree

8 files changed

+506
-88
lines changed

8 files changed

+506
-88
lines changed

Fifo1.hpp

+26-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <cassert>
34
#include <cstdlib>
45
#include <memory>
56

@@ -13,10 +14,10 @@ class Fifo1 : private Alloc
1314
using allocator_traits = std::allocator_traits<Alloc>;
1415
using size_type = typename allocator_traits::size_type;
1516

16-
explicit Fifo1(size_type size, Alloc const& alloc = Alloc{})
17+
explicit Fifo1(size_type capacity, Alloc const& alloc = Alloc{})
1718
: Alloc{alloc}
18-
, size_{size}
19-
, ring_{allocator_traits::allocate(*this, size)}
19+
, capacity_{capacity}
20+
, ring_{allocator_traits::allocate(*this, capacity)}
2021
{}
2122

2223
// For consistency with other fifos
@@ -27,23 +28,36 @@ class Fifo1 : private Alloc
2728

2829
~Fifo1() {
2930
while(not empty()) {
30-
ring_[popCursor_ % size_].~T();
31+
ring_[popCursor_ % capacity_].~T();
3132
++popCursor_;
3233
}
33-
allocator_traits::deallocate(*this, ring_, size_);
34+
allocator_traits::deallocate(*this, ring_, capacity_);
3435
}
3536

36-
auto size() const { return size_; }
37-
auto empty() const { return popCursor_ == pushCursor_; }
38-
auto full() const { return (pushCursor_ - popCursor_) == size_; }
37+
38+
/// Returns the number of elements in the fifo
39+
auto size() const noexcept {
40+
assert(popCursor_ <= pushCursor_);
41+
return pushCursor_ - popCursor_;
42+
}
43+
44+
/// Returns whether the container has no elements
45+
auto empty() const noexcept { return size() == 0; }
46+
47+
/// Returns whether the container has capacity_() elements
48+
auto full() const noexcept { return size() == capacity(); }
49+
50+
/// Returns the number of elements that can be held in the fifo
51+
auto capacity() const noexcept { return capacity_; }
52+
3953

4054
/// Push one object onto the fifo.
4155
/// @return `true` if the operation is successful; `false` if fifo is full.
4256
auto push(T const& value) {
4357
if (full()) {
4458
return false;
4559
}
46-
new (&ring_[pushCursor_ % size_]) T(value);
60+
new (&ring_[pushCursor_ % capacity_]) T(value);
4761
++pushCursor_;
4862
return true;
4963
}
@@ -54,14 +68,14 @@ class Fifo1 : private Alloc
5468
if (empty()) {
5569
return false;
5670
}
57-
value = ring_[popCursor_ % size_];
58-
ring_[popCursor_ % size_].~T();
71+
value = ring_[popCursor_ % capacity_];
72+
ring_[popCursor_ % capacity_].~T();
5973
++popCursor_;
6074
return true;
6175
}
6276

6377
private:
64-
size_type size_;
78+
size_type capacity_;
6579
T* ring_;
6680
size_type pushCursor_;
6781
size_type popCursor_;

Fifo2.hpp

+26-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <atomic>
4+
#include <cassert>
45
#include <memory>
56

67

@@ -13,31 +14,44 @@ class Fifo2 : private Alloc
1314
using allocator_traits = std::allocator_traits<Alloc>;
1415
using size_type = typename allocator_traits::size_type;
1516

16-
explicit Fifo2(size_type size, Alloc const& alloc = Alloc{})
17+
explicit Fifo2(size_type capacity, Alloc const& alloc = Alloc{})
1718
: Alloc{alloc}
18-
, size_{size}
19-
, ring_{allocator_traits::allocate(*this, size)}
19+
, capacity_{capacity}
20+
, ring_{allocator_traits::allocate(*this, capacity)}
2021
{}
2122

2223
~Fifo2() {
2324
while(not empty()) {
24-
ring_[popCursor_ % size_].~T();
25+
ring_[popCursor_ % capacity_].~T();
2526
++popCursor_;
2627
}
27-
allocator_traits::deallocate(*this, ring_, size_);
28+
allocator_traits::deallocate(*this, ring_, capacity_);
2829
}
2930

30-
auto size() const { return size_; }
31-
auto empty() const { return popCursor_ == pushCursor_; }
32-
auto full() const { return (pushCursor_ - popCursor_) == size_; }
31+
/// Returns the number of elements in the fifo
32+
auto size() const noexcept {
33+
assert(popCursor_ <= pushCursor_);
34+
return pushCursor_ - popCursor_;
35+
}
36+
37+
/// Returns whether the container has no elements
38+
auto empty() const noexcept { return size() == 0; }
39+
40+
/// Returns whether the container has capacity_() elements
41+
auto full() const noexcept { return size() == capacity(); }
42+
43+
/// Returns the number of elements that can be held in the fifo
44+
auto capacity() const noexcept { return capacity_; }
45+
46+
3347

3448
/// Push one object onto the fifo.
3549
/// @return `true` if the operation is successful; `false` if fifo is full.
3650
auto push(T const& value) {
3751
if (full()) {
3852
return false;
3953
}
40-
new (&ring_[pushCursor_ % size_]) T(value);
54+
new (&ring_[pushCursor_ % capacity_]) T(value);
4155
++pushCursor_;
4256
return true;
4357
}
@@ -48,14 +62,14 @@ class Fifo2 : private Alloc
4862
if (empty()) {
4963
return false;
5064
}
51-
value = ring_[popCursor_ % size_];
52-
ring_[popCursor_ % size_].~T();
65+
value = ring_[popCursor_ % capacity_];
66+
ring_[popCursor_ % capacity_].~T();
5367
++popCursor_;
5468
return true;
5569
}
5670

5771
private:
58-
size_type size_;
72+
size_type capacity_;
5973
T* ring_;
6074

6175
using CursorType = std::atomic<size_type>;

Fifo3.hpp

+37-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <atomic>
4+
#include <cassert>
45
#include <memory>
56
#include <new>
67

@@ -14,23 +15,39 @@ class Fifo3 : private Alloc
1415
using allocator_traits = std::allocator_traits<Alloc>;
1516
using size_type = typename allocator_traits::size_type;
1617

17-
explicit Fifo3(size_type size, Alloc const& alloc = Alloc{})
18+
explicit Fifo3(size_type capacity, Alloc const& alloc = Alloc{})
1819
: Alloc{alloc}
19-
, size_{size}
20-
, ring_{allocator_traits::allocate(*this, size)}
20+
, capacity_{capacity}
21+
, ring_{allocator_traits::allocate(*this, capacity)}
2122
{}
2223

2324
~Fifo3() {
2425
while(not empty()) {
25-
ring_[popCursor_ % size_].~T();
26+
ring_[popCursor_ % capacity_].~T();
2627
++popCursor_;
2728
}
28-
allocator_traits::deallocate(*this, ring_, size_);
29+
allocator_traits::deallocate(*this, ring_, capacity_);
2930
}
3031

31-
auto size() const { return size_; }
32-
auto empty() const { return popCursor_ == pushCursor_; }
33-
auto full() const { return (pushCursor_ - popCursor_) == size_; }
32+
33+
/// Returns the number of elements in the fifo
34+
auto size() const noexcept {
35+
auto pushCursor = pushCursor_.load(std::memory_order_acquire);
36+
auto popCursor = popCursor_.load(std::memory_order_relaxed);
37+
38+
assert(popCursor <= pushCursor);
39+
return pushCursor - popCursor;
40+
}
41+
42+
/// Returns whether the container has no elements
43+
auto empty() const noexcept { return size() == 0; }
44+
45+
/// Returns whether the container has capacity_() elements
46+
auto full() const noexcept { return size() == capacity(); }
47+
48+
/// Returns the number of elements that can be held in the fifo
49+
auto capacity() const noexcept { return capacity_; }
50+
3451

3552
/// Push one object onto the fifo.
3653
/// @return `true` if the operation is successful; `false` if fifo is full.
@@ -40,7 +57,7 @@ class Fifo3 : private Alloc
4057
if (full(pushCursor, popCursor)) {
4158
return false;
4259
}
43-
new (&ring_[pushCursor % size_]) T(value);
60+
new (&ring_[pushCursor % capacity_]) T(value);
4461
pushCursor_.store(pushCursor + 1, std::memory_order_release);
4562
return true;
4663
}
@@ -53,39 +70,41 @@ class Fifo3 : private Alloc
5370
if (empty(pushCursor, popCursor)) {
5471
return false;
5572
}
56-
value = ring_[popCursor % size_];
57-
ring_[popCursor % size_].~T();
73+
value = ring_[popCursor % capacity_];
74+
ring_[popCursor % capacity_].~T();
5875
popCursor_.store(popCursor + 1, std::memory_order_release);
5976
return true;
6077
}
6178

6279
private:
63-
auto full(size_type pushCursor, size_type popCursor) const {
64-
return (pushCursor - popCursor) == size_;
80+
auto full(size_type pushCursor, size_type popCursor) const noexcept {
81+
return (pushCursor - popCursor) == capacity_;
6582
}
66-
static auto empty(size_type pushCursor, size_type popCursor) {
83+
static auto empty(size_type pushCursor, size_type popCursor) noexcept {
6784
return pushCursor == popCursor;
6885
}
6986

7087
private:
71-
size_type size_;
88+
size_type capacity_;
7289
T* ring_;
7390

7491
using CursorType = std::atomic<size_type>;
7592
static_assert(CursorType::is_always_lock_free);
7693

77-
// https://stackoverflow.com/questions/39680206/understanding-stdhardware-destructive-interference-size-and-stdhardware-cons
78-
// And this:
94+
// N.B. std::hardware_destructive_interference_size is not used directly
7995
// error: use of ‘std::hardware_destructive_interference_size’ [-Werror=interference-size]
8096
// note: its value can vary between compiler versions or with different ‘-mtune’ or ‘-mcpu’ flags
8197
// note: if this use is part of a public ABI, change it to instead use a constant variable you define
8298
// note: the default value for the current CPU tuning is 64 bytes
8399
// 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};
100+
static constexpr auto hardware_destructive_interference_size = size_type{64};
85101

86102
/// Loaded and stored by the push thread; loaded by the pop thread
87103
alignas(hardware_destructive_interference_size) CursorType pushCursor_;
88104

89105
/// Loaded and stored by the pop thread; loaded by the push thread
90106
alignas(hardware_destructive_interference_size) CursorType popCursor_;
107+
108+
// Padding to avoid false sharing with adjacent objects
109+
char padding_[hardware_destructive_interference_size - sizeof(size_type)];
91110
};

Fifo4.hpp

+36-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <atomic>
4+
#include <cassert>
45
#include <memory>
56
#include <new>
67

@@ -14,23 +15,38 @@ class Fifo4 : private Alloc
1415
using allocator_traits = std::allocator_traits<Alloc>;
1516
using size_type = typename allocator_traits::size_type;
1617

17-
explicit Fifo4(size_type size, Alloc const& alloc = Alloc{})
18+
explicit Fifo4(size_type capacity, Alloc const& alloc = Alloc{})
1819
: Alloc{alloc}
19-
, size_{size}
20-
, ring_{allocator_traits::allocate(*this, size_)}
20+
, capacity_{capacity}
21+
, ring_{allocator_traits::allocate(*this, capacity)}
2122
{}
2223

2324
~Fifo4() {
2425
while(not empty()) {
25-
ring_[popCursor_ % size_].~T();
26+
ring_[popCursor_ % capacity_].~T();
2627
++popCursor_;
2728
}
28-
allocator_traits::deallocate(*this, ring_, size_);
29+
allocator_traits::deallocate(*this, ring_, capacity_);
2930
}
3031

31-
auto size() const { return size_; }
32-
auto empty() const { return popCursor_ == pushCursor_; }
33-
auto full() const { return (pushCursor_ - popCursor_) == size_; }
32+
/// Returns the number of elements in the fifo
33+
auto size() const noexcept {
34+
auto pushCursor = pushCursor_.load(std::memory_order_acquire);
35+
auto popCursor = popCursor_.load(std::memory_order_relaxed);
36+
37+
assert(popCursor <= pushCursor);
38+
return pushCursor - popCursor;
39+
}
40+
41+
/// Returns whether the container has no elements
42+
auto empty() const noexcept { return size() == 0; }
43+
44+
/// Returns whether the container has capacity_() elements
45+
auto full() const noexcept { return size() == capacity(); }
46+
47+
/// Returns the number of elements that can be held in the fifo
48+
auto capacity() const noexcept { return capacity_; }
49+
3450

3551
/// Push one object onto the fifo.
3652
/// @return `true` if the operation is successful; `false` if fifo is full.
@@ -43,7 +59,7 @@ class Fifo4 : private Alloc
4359
return false;
4460
}
4561

46-
new (&ring_[pushCursor % size_]) T(value);
62+
new (&ring_[pushCursor % capacity_]) T(value);
4763
pushCursor_.store(pushCursor + 1, std::memory_order_release);
4864
return true;
4965
}
@@ -59,29 +75,29 @@ class Fifo4 : private Alloc
5975
return false;
6076
}
6177

62-
value = ring_[popCursor % size_];
63-
ring_[popCursor % size_].~T();
78+
value = ring_[popCursor % capacity_];
79+
ring_[popCursor % capacity_].~T();
6480
popCursor_.store(popCursor + 1, std::memory_order_release);
6581
return true;
6682
}
6783

6884
private:
69-
auto full(size_type pushCursor, size_type popCursor) const {
70-
return (pushCursor - popCursor) == size_;
85+
auto full(size_type pushCursor, size_type popCursor) const noexcept {
86+
return (pushCursor - popCursor) == capacity_;
7187
}
72-
static auto empty(size_type pushCursor, size_type popCursor) {
88+
static auto empty(size_type pushCursor, size_type popCursor) noexcept {
7389
return pushCursor == popCursor;
7490
}
7591

7692
private:
77-
size_type size_;
93+
size_type capacity_;
7894
T* ring_;
7995

8096
using CursorType = std::atomic<size_type>;
8197
static_assert(CursorType::is_always_lock_free);
8298

83-
// https://stackoverflow.com/questions/39680206/understanding-stdhardware-destructive-interference-size-and-stdhardware-cons
84-
static constexpr auto hardware_destructive_interference_size = size_type{128};
99+
// See Fifo3 for reason std::hardware_destructive_interference_size is not used directly
100+
static constexpr auto hardware_destructive_interference_size = size_type{64};
85101

86102
/// Loaded and stored by the push thread; loaded by the pop thread
87103
alignas(hardware_destructive_interference_size) CursorType pushCursor_;
@@ -94,4 +110,7 @@ class Fifo4 : private Alloc
94110

95111
/// Exclusive to the pop thread
96112
alignas(hardware_destructive_interference_size) size_type pushCursorCached_{};
113+
114+
// Padding to avoid false sharing with adjacent objects
115+
char padding_[hardware_destructive_interference_size - sizeof(size_type)];
97116
};

0 commit comments

Comments
 (0)