| 1 | #include <boost/thread/condition_variable.hpp>
 | 
|---|
| 2 | #include <boost/thread/mutex.hpp>
 | 
|---|
| 3 | 
 | 
|---|
| 4 | #include <condition_variable>
 | 
|---|
| 5 | #include <future>
 | 
|---|
| 6 | #include <limits>
 | 
|---|
| 7 | #include <cstdio>
 | 
|---|
| 8 | #include <thread>
 | 
|---|
| 9 | #include <mutex>
 | 
|---|
| 10 | 
 | 
|---|
| 11 | ////////////////////////////////////////////////////////////////////////////////////////////////
 | 
|---|
| 12 | 
 | 
|---|
| 13 | namespace {
 | 
|---|
| 14 | 
 | 
|---|
| 15 | ////////////////////////////////////////////////////////////////////////////////////////////////
 | 
|---|
| 16 | 
 | 
|---|
| 17 | class Stopwatch
 | 
|---|
| 18 | {
 | 
|---|
| 19 | public:
 | 
|---|
| 20 |     typedef long long nsec_t;
 | 
|---|
| 21 | 
 | 
|---|
| 22 |     static nsec_t now() {
 | 
|---|
| 23 |         timespec ts;
 | 
|---|
| 24 |         if(clock_gettime(CLOCK_MONOTONIC, &ts))
 | 
|---|
| 25 |             abort();
 | 
|---|
| 26 |         return ts.tv_sec * nsec_t(1000000000) + ts.tv_nsec;
 | 
|---|
| 27 |     }
 | 
|---|
| 28 | 
 | 
|---|
| 29 |     Stopwatch()
 | 
|---|
| 30 |         : start_(now())
 | 
|---|
| 31 |     {}
 | 
|---|
| 32 | 
 | 
|---|
| 33 |     nsec_t elapsed() const {
 | 
|---|
| 34 |         return now() - start_;
 | 
|---|
| 35 |     }
 | 
|---|
| 36 | 
 | 
|---|
| 37 | private:
 | 
|---|
| 38 |     nsec_t start_;
 | 
|---|
| 39 | };
 | 
|---|
| 40 | 
 | 
|---|
| 41 | ////////////////////////////////////////////////////////////////////////////////////////////////
 | 
|---|
| 42 | 
 | 
|---|
| 43 | struct BoostTypes
 | 
|---|
| 44 | {
 | 
|---|
| 45 |     typedef boost::condition_variable condition_variable;
 | 
|---|
| 46 |     typedef boost::mutex mutex;
 | 
|---|
| 47 |     typedef boost::mutex::scoped_lock scoped_lock;
 | 
|---|
| 48 | };
 | 
|---|
| 49 | 
 | 
|---|
| 50 | struct StdTypes
 | 
|---|
| 51 | {
 | 
|---|
| 52 |     typedef std::condition_variable condition_variable;
 | 
|---|
| 53 |     typedef std::mutex mutex;
 | 
|---|
| 54 |     typedef std::unique_lock<std::mutex> scoped_lock;
 | 
|---|
| 55 | };
 | 
|---|
| 56 | 
 | 
|---|
| 57 | template<class Types>
 | 
|---|
| 58 | struct SharedData : Types
 | 
|---|
| 59 | {
 | 
|---|
| 60 |     unsigned const iterations;
 | 
|---|
| 61 |     unsigned counter;
 | 
|---|
| 62 |     unsigned semaphore;
 | 
|---|
| 63 |     typename Types::condition_variable cnd;
 | 
|---|
| 64 |     typename Types::mutex mtx;
 | 
|---|
| 65 |     Stopwatch::nsec_t producer_time;
 | 
|---|
| 66 | 
 | 
|---|
| 67 |     SharedData(unsigned iterations, unsigned consumers)
 | 
|---|
| 68 |         : iterations(iterations)
 | 
|---|
| 69 |         , counter()
 | 
|---|
| 70 |         , semaphore(consumers) // Initialize to the number of consumers. (*)
 | 
|---|
| 71 |         , producer_time()
 | 
|---|
| 72 |     {}
 | 
|---|
| 73 | };
 | 
|---|
| 74 | 
 | 
|---|
| 75 | ////////////////////////////////////////////////////////////////////////////////////////////////
 | 
|---|
| 76 | 
 | 
|---|
| 77 | template<class S>
 | 
|---|
| 78 | void producer_thread(S* shared_data) {
 | 
|---|
| 79 |     Stopwatch sw;
 | 
|---|
| 80 | 
 | 
|---|
| 81 |     unsigned const consumers = shared_data->semaphore; // (*)
 | 
|---|
| 82 |     for(unsigned i = shared_data->iterations; i--;) {
 | 
|---|
| 83 |         {
 | 
|---|
| 84 |             typename S::scoped_lock lock(shared_data->mtx);
 | 
|---|
| 85 |             // Wait till all consumers signal.
 | 
|---|
| 86 |             while(consumers != shared_data->semaphore)
 | 
|---|
| 87 |                 shared_data->cnd.wait(lock);
 | 
|---|
| 88 |             shared_data->semaphore = 0;
 | 
|---|
| 89 |             // Signal consumers.
 | 
|---|
| 90 |             ++shared_data->counter;
 | 
|---|
| 91 |         }
 | 
|---|
| 92 |         shared_data->cnd.notify_all();
 | 
|---|
| 93 |     }
 | 
|---|
| 94 | 
 | 
|---|
| 95 |     shared_data->producer_time = sw.elapsed();
 | 
|---|
| 96 | }
 | 
|---|
| 97 | 
 | 
|---|
| 98 | template<class S>
 | 
|---|
| 99 | void consumer_thread(S* shared_data) {
 | 
|---|
| 100 |     unsigned counter = 0;
 | 
|---|
| 101 |     while(counter != shared_data->iterations) {
 | 
|---|
| 102 |         {
 | 
|---|
| 103 |             typename S::scoped_lock lock(shared_data->mtx);
 | 
|---|
| 104 |             // Wait till the producer signals.
 | 
|---|
| 105 |             while(counter == shared_data->counter)
 | 
|---|
| 106 |                 shared_data->cnd.wait(lock);
 | 
|---|
| 107 |             counter = shared_data->counter;
 | 
|---|
| 108 |             // Signal the producer.
 | 
|---|
| 109 |             ++shared_data->semaphore;
 | 
|---|
| 110 |         }
 | 
|---|
| 111 |         shared_data->cnd.notify_all();
 | 
|---|
| 112 |     }
 | 
|---|
| 113 | }
 | 
|---|
| 114 | 
 | 
|---|
| 115 | ////////////////////////////////////////////////////////////////////////////////////////////////
 | 
|---|
| 116 | 
 | 
|---|
| 117 | template<class Types>
 | 
|---|
| 118 | Stopwatch::nsec_t benchmark_ping_pong(unsigned consumer_count) {
 | 
|---|
| 119 |     typedef SharedData<Types> S;
 | 
|---|
| 120 | 
 | 
|---|
| 121 |     auto best_producer_time = std::numeric_limits<Stopwatch::nsec_t>::max();
 | 
|---|
| 122 | 
 | 
|---|
| 123 |     std::vector<std::thread> consumers{consumer_count};
 | 
|---|
| 124 | 
 | 
|---|
| 125 |     // Run the benchmark 3 times and report the best time.
 | 
|---|
| 126 |     for(int times = 3; times--;) {
 | 
|---|
| 127 |         S shared_data{100000, consumer_count};
 | 
|---|
| 128 | 
 | 
|---|
| 129 |         // Start the consumers.
 | 
|---|
| 130 |         for(unsigned i = 0; i < consumer_count; ++i)
 | 
|---|
| 131 |             consumers[i] = std::thread{consumer_thread<S>, &shared_data};
 | 
|---|
| 132 |         // Start the producer and wait till it finishes.
 | 
|---|
| 133 |         std::thread{producer_thread<S>, &shared_data}.join();
 | 
|---|
| 134 |         // Wait till consumers finish.
 | 
|---|
| 135 |         for(unsigned i = 0; i < consumer_count; ++i)
 | 
|---|
| 136 |             consumers[i].join();
 | 
|---|
| 137 | 
 | 
|---|
| 138 |         best_producer_time = std::min(best_producer_time, shared_data.producer_time);
 | 
|---|
| 139 |     }
 | 
|---|
| 140 | 
 | 
|---|
| 141 |     return best_producer_time;
 | 
|---|
| 142 | }
 | 
|---|
| 143 | 
 | 
|---|
| 144 | ////////////////////////////////////////////////////////////////////////////////////////////////
 | 
|---|
| 145 | 
 | 
|---|
| 146 | } // namespace
 | 
|---|
| 147 | 
 | 
|---|
| 148 | ////////////////////////////////////////////////////////////////////////////////////////////////
 | 
|---|
| 149 | 
 | 
|---|
| 150 | // sudo chrt -f 99 /usr/bin/time -f "\n***\ntime: %E\ncontext switches: %c\nwaits: %w" /home/max/otsquant/build/Linux-x86_64-64.g++-release/test/test
 | 
|---|
| 151 | 
 | 
|---|
| 152 | /*
 | 
|---|
| 153 | 
 | 
|---|
| 154 | Producer-consumer ping-pong tests. It aims to benchmark condition variables with and without
 | 
|---|
| 155 | thread cancellation support by comparing the time it took to complete the benchmark.
 | 
|---|
| 156 | 
 | 
|---|
| 157 | Condition variable with thread cancellation support is boost::condition_variable from
 | 
|---|
| 158 | boost-1.51. Without - std::condition_variable that comes with gcc-4.7.2.
 | 
|---|
| 159 | 
 | 
|---|
| 160 | One producer, one to CONSUMER_MAX consumers. The benchmark calls
 | 
|---|
| 161 | condition_variable::notify_all() without holding a mutex to maximize contention within this
 | 
|---|
| 162 | function. Each benchmark for a number of consumers is run three times and the best time is
 | 
|---|
| 163 | picked to get rid of outliers.
 | 
|---|
| 164 | 
 | 
|---|
| 165 | The results are reported for each benchmark for a number of consumers. The most important number
 | 
|---|
| 166 | is (std - boost) / std * 100. Positive numbers are when boost::condition_variable is faster,
 | 
|---|
| 167 | negative it is slower.
 | 
|---|
| 168 | 
 | 
|---|
| 169 |  */
 | 
|---|
| 170 | 
 | 
|---|
| 171 | int main() {
 | 
|---|
| 172 |     enum { CONSUMER_MAX = 20 };
 | 
|---|
| 173 | 
 | 
|---|
| 174 |     struct {
 | 
|---|
| 175 |         Stopwatch::nsec_t boost, std;
 | 
|---|
| 176 |     } best_times[CONSUMER_MAX] = {};
 | 
|---|
| 177 | 
 | 
|---|
| 178 |     for(unsigned i = 1; i <= CONSUMER_MAX; ++i) {
 | 
|---|
| 179 |         auto& b = best_times[i - 1];
 | 
|---|
| 180 |         b.std = benchmark_ping_pong<StdTypes>(i);
 | 
|---|
| 181 |         b.boost = benchmark_ping_pong<BoostTypes>(i);
 | 
|---|
| 182 | 
 | 
|---|
| 183 |         std::printf("consumers:                 %4d\n", i);
 | 
|---|
| 184 |         std::printf("best std producer time:   %15.9fsec\n", b.std * 1e-9);
 | 
|---|
| 185 |         std::printf("best boost producer time: %15.9fsec\n", b.boost * 1e-9);
 | 
|---|
| 186 |         std::printf("(std - boost) / std:       %7.2f%%\n", (b.std - b.boost) * 100. / b.std);
 | 
|---|
| 187 |     }
 | 
|---|
| 188 | 
 | 
|---|
| 189 |     printf("\ncsv:\n\n");
 | 
|---|
| 190 |     printf("consumers,(std-boost)/std,std,boost\n");
 | 
|---|
| 191 |     for(unsigned i = 1; i <= CONSUMER_MAX; ++i) {
 | 
|---|
| 192 |         auto& b = best_times[i - 1];
 | 
|---|
| 193 |         printf("%d,%f,%lld,%lld\n", i, (b.std - b.boost) * 100. /  b.std, b.std, b.boost);
 | 
|---|
| 194 |     }
 | 
|---|
| 195 | }
 | 
|---|