1 | // (C) Copyright 2012 Vicente J. Botet Escriba |
2 | // |
3 | // Distributed under the Boost Software License, Version 1.0. (See |
4 | // accompanying file LICENSE_1_0.txt or copy at |
5 | // http://www.boost.org/LICENSE_1_0.txt) |
6 | // |
7 | // This performance test is based on the performance test provided by maxim.yegorushkin |
8 | // at https://svn.boost.org/trac/boost/ticket/7422 |
9 | |
10 | #define BOOST_THREAD_DONT_PROVIDE_INTERRUPTIONS |
11 | |
12 | #include <boost/thread/condition_variable.hpp> |
13 | #include <boost/thread/mutex.hpp> |
14 | #include <boost/chrono/stopwatches/simple_stopwatch.hpp> |
15 | |
16 | #include <condition_variable> |
17 | #include <future> |
18 | #include <limits> |
19 | #include <cstdio> |
20 | #include <thread> |
21 | #include <mutex> |
22 | #include <vector> |
23 | |
24 | //////////////////////////////////////////////////////////////////////////////////////////////// |
25 | |
26 | namespace |
27 | { |
28 | //////////////////////////////////////////////////////////////////////////////////////////////// |
29 | |
30 | // class Stopwatch |
31 | // { |
32 | // public: |
33 | // typedef long long rep; |
34 | // |
35 | // static rep now() |
36 | // { |
37 | // timespec ts; |
38 | // if (clock_gettime(CLOCK_MONOTONIC, &ts)) abort(); |
39 | // return ts.tv_sec * rep(1000000000) + ts.tv_nsec; |
40 | // } |
41 | // |
42 | // Stopwatch() : |
43 | // start_(now()) |
44 | // { |
45 | // } |
46 | // |
47 | // rep elapsed() const |
48 | // { |
49 | // return now() - start_; |
50 | // } |
51 | // |
52 | // private: |
53 | // rep start_; |
54 | // }; |
55 | |
56 | typedef boost::chrono::simple_stopwatch<> Stopwatch; |
57 | //////////////////////////////////////////////////////////////////////////////////////////////// |
58 | |
59 | struct BoostTypes |
60 | { |
61 | typedef boost::condition_variable condition_variable; |
62 | typedef boost::mutex mutex; |
63 | typedef boost::mutex::scoped_lock scoped_lock; |
64 | }; |
65 | |
66 | struct StdTypes |
67 | { |
68 | typedef std::condition_variable condition_variable; |
69 | typedef std::mutex mutex; |
70 | typedef std::unique_lock<std::mutex> scoped_lock; |
71 | }; |
72 | |
73 | template <class Types> |
74 | struct SharedData: Types |
75 | { |
76 | unsigned const iterations; |
77 | unsigned counter; |
78 | unsigned semaphore; |
79 | typename Types::condition_variable cnd; |
80 | typename Types::mutex mtx; |
81 | Stopwatch::rep producer_time; |
82 | |
83 | SharedData(unsigned iterations, unsigned consumers) : |
84 | iterations(iterations), counter(), semaphore(consumers) // Initialize to the number of consumers. (*) |
85 | , producer_time() |
86 | { |
87 | } |
88 | }; |
89 | |
90 | |
91 | //////////////////////////////////////////////////////////////////////////////////////////////// |
92 | |
93 | template <class S> |
94 | void producer_thread(S* shared_data) |
95 | { |
96 | Stopwatch sw; |
97 | |
98 | unsigned const consumers = shared_data->semaphore; // (*) |
99 | for (unsigned i = shared_data->iterations; i--;) |
100 | { |
101 | { |
102 | typename S::scoped_lock lock(shared_data->mtx); |
103 | // Wait till all consumers signal. |
104 | while (consumers != shared_data->semaphore) |
105 | { |
106 | shared_data->cnd.wait(lock); |
107 | } |
108 | shared_data->semaphore = 0; |
109 | // Signal consumers. |
110 | ++shared_data->counter; |
111 | } |
112 | shared_data->cnd.notify_all(); |
113 | } |
114 | |
115 | shared_data->producer_time = sw.elapsed().count(); |
116 | } |
117 | |
118 | template <class S> |
119 | void consumer_thread(S* shared_data) |
120 | { |
121 | unsigned counter = 0; |
122 | while (counter != shared_data->iterations) |
123 | { |
124 | { |
125 | typename S::scoped_lock lock(shared_data->mtx); |
126 | // Wait till the producer signals. |
127 | while (counter == shared_data->counter) |
128 | { |
129 | shared_data->cnd.wait(lock); |
130 | } |
131 | counter = shared_data->counter; |
132 | // Signal the producer. |
133 | ++shared_data->semaphore; |
134 | } |
135 | shared_data->cnd.notify_all(); |
136 | } |
137 | } |
138 | |
139 | //////////////////////////////////////////////////////////////////////////////////////////////// |
140 | |
141 | template <class Types> |
142 | Stopwatch::rep benchmark_ping_pong(unsigned consumer_count) |
143 | { |
144 | typedef SharedData<Types> S; |
145 | |
146 | auto best_producer_time = std::numeric_limits<Stopwatch::rep>::max BOOST_PREVENT_MACRO_SUBSTITUTION (); |
147 | |
148 | std::vector<std::thread> consumers |
149 | { consumer_count }; |
150 | |
151 | // Run the benchmark 10 times and report the best time. |
152 | for (int times = 10; times--;) |
153 | { |
154 | S shared_data |
155 | { 100000, consumer_count }; |
156 | |
157 | // Start the consumers. |
158 | for (unsigned i = 0; i < consumer_count; ++i) |
159 | consumers[i] = std::thread |
160 | { consumer_thread<S> , &shared_data }; |
161 | // Start the producer and wait till it finishes. |
162 | std::thread |
163 | { producer_thread<S> , &shared_data }.join(); |
164 | // Wait till consumers finish. |
165 | for (unsigned i = 0; i < consumer_count; ++i) |
166 | consumers[i].join(); |
167 | |
168 | best_producer_time = std::min BOOST_PREVENT_MACRO_SUBSTITUTION (best_producer_time, shared_data.producer_time); |
169 | |
170 | } |
171 | |
172 | return best_producer_time; |
173 | } |
174 | |
175 | //////////////////////////////////////////////////////////////////////////////////////////////// |
176 | |
177 | } // namespace |
178 | |
179 | //////////////////////////////////////////////////////////////////////////////////////////////// |
180 | |
181 | // 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 |
182 | |
183 | /* |
184 | |
185 | Producer-consumer ping-pong tests. It aims to benchmark condition variables with and without |
186 | thread cancellation support by comparing the time it took to complete the benchmark. |
187 | |
188 | Condition variable with thread cancellation support is boost::condition_variable from |
189 | boost-1.51. Without - std::condition_variable that comes with gcc-4.7.2. |
190 | |
191 | One producer, one to CONSUMER_MAX consumers. The benchmark calls |
192 | condition_variable::notify_all() without holding a mutex to maximize contention within this |
193 | function. Each benchmark for a number of consumers is run three times and the best time is |
194 | picked to get rid of outliers. |
195 | |
196 | The results are reported for each benchmark for a number of consumers. The most important number |
197 | is (std - boost) / std * 100. Positive numbers are when boost::condition_variable is faster, |
198 | negative it is slower. |
199 | |
200 | */ |
201 | |
202 | int main() |
203 | { |
204 | std::printf(format: "MAIN\n" ); |
205 | enum |
206 | { |
207 | CONSUMER_MAX = 2 |
208 | }; |
209 | |
210 | struct |
211 | { |
212 | Stopwatch::rep boost, std; |
213 | } best_times[CONSUMER_MAX] = {}; |
214 | |
215 | for (unsigned i = 1; i <= CONSUMER_MAX; ++i) |
216 | { |
217 | auto& b = best_times[i - 1]; |
218 | std::printf(format: "STD: %d\n" , i); |
219 | b.std = benchmark_ping_pong<StdTypes> (i); |
220 | std::printf(format: "BOOST: %d\n" , i); |
221 | b.boost = benchmark_ping_pong<BoostTypes> (i); |
222 | |
223 | std::printf(format: "consumers: %4d\n" , i); |
224 | std::printf(format: "best std producer time: %15.9fsec\n" , b.std * 1e-9); |
225 | std::printf(format: "best boost producer time: %15.9fsec\n" , b.boost * 1e-9); |
226 | std::printf(format: "(std - boost) / std: %7.2f%%\n" , (b.std - b.boost) * 100. / b.std); |
227 | } |
228 | |
229 | printf(format: "\ncsv:\n\n" ); |
230 | printf(format: "consumers,(std-boost)/std,std,boost\n" ); |
231 | for (unsigned i = 1; i <= CONSUMER_MAX; ++i) |
232 | { |
233 | auto& b = best_times[i - 1]; |
234 | printf("%d,%f,%lld,%lld\n" , i, (b.std - b.boost) * 100. / b.std, b.std, b.boost); |
235 | } |
236 | return 1; |
237 | } |
238 | |