xref: /oneTBB/test/common/spin_barrier.h (revision a080baf9)
1 /*
2     Copyright (c) 2005-2021 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #ifndef __TBB_test_common_spin_barrier_H
18 #define __TBB_test_common_spin_barrier_H
19 
20 #include "config.h"
21 
22 // Do not replace this include by doctest.h
23 // This headers includes inside benchmark.h and used for benchmarking based on Celero
24 // Using of both DocTest and Celero frameworks caused unexpected compilation errors.
25 #include "utils_assert.h"
26 #include "utils_yield.h"
27 
28 #include "oneapi/tbb/detail/_machine.h"
29 #include "oneapi/tbb/detail/_utils.h"
30 #include "oneapi/tbb/tick_count.h"
31 
32 #include <atomic>
33 #include <thread>
34 
35 namespace utils {
36 
37 //! Spin WHILE predicate returns true
38 template <typename Predicate>
SpinWaitWhile(Predicate pred)39 void SpinWaitWhile(Predicate pred) {
40     int count = 0;
41     while (pred()) {
42         if (count < 100) {
43             tbb::detail::machine_pause(10);
44             ++count;
45         } else if (count < 200) {
46             utils::yield();
47             ++count;
48         } else {
49             std::this_thread::sleep_for(std::chrono::microseconds(count/100));
50             if (count < 10000) {
51                 count += 100;
52             }
53         }
54     }
55 }
56 
57 //! Spin WHILE the condition is true.
58 template <typename T, typename C>
SpinWaitWhileCondition(const std::atomic<T> & location,C comp)59 void SpinWaitWhileCondition(const std::atomic<T>& location, C comp) {
60     SpinWaitWhile([&] { return comp(location.load(std::memory_order_acquire)); });
61 }
62 
63 //! Spin WHILE the value of the variable is equal to a given value
64 /** T and U should be comparable types. */
65 template <typename T, typename U>
SpinWaitWhileEq(const std::atomic<T> & location,const U value)66 void SpinWaitWhileEq(const std::atomic<T>& location, const U value) {
67     SpinWaitWhileCondition(location, [&value](T t) { return t == value; });
68 }
69 
70 //! Spin UNTIL the value of the variable is equal to a given value
71 /** T and U should be comparable types. */
72 template<typename T, typename U>
SpinWaitUntilEq(const std::atomic<T> & location,const U value)73 void SpinWaitUntilEq(const std::atomic<T>& location, const U value) {
74     SpinWaitWhileCondition(location, [&value](T t) { return t != value; });
75 }
76 
77 class WaitWhileEq {
78 public:
79     //! Assignment not allowed
80     void operator=( const WaitWhileEq& ) = delete;
81 
82     template<typename T, typename U>
operator()83     void operator()( const std::atomic<T>& location, U value ) const {
84         SpinWaitWhileEq(location, value);
85     }
86 };
87 
88 class SpinBarrier {
89 public:
90     using size_type = std::size_t;
91 private:
92     size_type myNumThreads;
93     std::atomic<size_type> myNumThreadsFinished; // reached the barrier in this epoch
94     // the number of times the barrier was opened
95     std::atomic<size_type> myEpoch;
96     std::atomic<size_type> myLifeTimeGuard;
97     // a throwaway barrier can be used only once, then wait() becomes a no-op
98     bool myThrowaway;
99 
100     struct DummyCallback {
operatorDummyCallback101         void operator() () const {}
102         template<typename T, typename U>
operatorDummyCallback103         void operator()( const T&, U) const {}
104     };
105 
106 public:
107     SpinBarrier( const SpinBarrier& ) = delete;    // no copy ctor
108     SpinBarrier& operator=( const SpinBarrier& ) = delete; // no assignment
109 
~SpinBarrier()110     ~SpinBarrier() {
111         while (myLifeTimeGuard.load(std::memory_order_acquire)) {}
112     }
113 
114     SpinBarrier( size_type nthreads = 0, bool throwaway = false ) {
115         initialize(nthreads, throwaway);
116     }
117 
118     void initialize( size_type nthreads, bool throwaway = false ) {
119         myNumThreads = nthreads;
120         myNumThreadsFinished = 0;
121         myEpoch = 0;
122         myThrowaway = throwaway;
123         myLifeTimeGuard = 0;
124     }
125 
126     // Returns whether this thread was the last to reach the barrier.
127     // onWaitCallback is called by a thread for waiting;
128     // onOpenBarrierCallback is called by the last thread before unblocking other threads.
129     template<typename WaitEq, typename Callback>
customWait(const WaitEq & onWaitCallback,const Callback & onOpenBarrierCallback)130     bool customWait( const WaitEq& onWaitCallback, const Callback& onOpenBarrierCallback ) {
131         if (myThrowaway && myEpoch) {
132             return false;
133         }
134 
135         size_type epoch = myEpoch.load(std::memory_order_relaxed);
136         int threadsLeft = static_cast<int>(myNumThreads - myNumThreadsFinished.fetch_add(1, std::memory_order_release) - 1);
137         ASSERT(threadsLeft >= 0,"Broken barrier");
138         if (threadsLeft > 0) {
139             /* this thread is not the last; wait until the epoch changes & return false */
140             onWaitCallback(myEpoch, epoch); // acquire myEpoch
141             myLifeTimeGuard.fetch_sub(1, std::memory_order_release);
142             return false;
143         }
144         /* reset the barrier, increment the epoch, and return true */
145         threadsLeft = static_cast<int>(myNumThreadsFinished.fetch_sub(myNumThreads, std::memory_order_acquire) - myNumThreads);
146         ASSERT(threadsLeft == 0,"Broken barrier");
147         /* This thread is the last one at the barrier in this epoch */
148         onOpenBarrierCallback();
149         /* wakes up threads waiting to exit in this epoch */
150         myLifeTimeGuard.fetch_add(myNumThreads - 1, std::memory_order_relaxed);
151         epoch -= myEpoch.fetch_add(1, std::memory_order_release);
152         ASSERT(epoch == 0,"Broken barrier");
153         return true;
154     }
155 
156     // onOpenBarrierCallback is called by the last thread before unblocking other threads.
157     template<typename Callback>
wait(const Callback & onOpenBarrierCallback)158     bool wait( const Callback& onOpenBarrierCallback ) {
159         return customWait(WaitWhileEq(), onOpenBarrierCallback);
160     }
161 
wait()162     bool wait() {
163         return wait(DummyCallback());
164     }
165 
166     // Signal to the barrier, rather a semaphore functionality.
signalNoWait()167     bool signalNoWait() {
168         return customWait(DummyCallback(), DummyCallback());
169     }
170 };
171 
172 } // utils
173 
174 #endif // __TBB_test_common_spin_barrier_H
175