Ticket #7422: test.cc

File test.cc, 5.9 KB (added by maxim.yegorushkin@…, 10 years ago)

The benchmark

Line 
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
13namespace {
14
15////////////////////////////////////////////////////////////////////////////////////////////////
16
17class Stopwatch
18{
19public:
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
37private:
38 nsec_t start_;
39};
40
41////////////////////////////////////////////////////////////////////////////////////////////////
42
43struct BoostTypes
44{
45 typedef boost::condition_variable condition_variable;
46 typedef boost::mutex mutex;
47 typedef boost::mutex::scoped_lock scoped_lock;
48};
49
50struct 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
57template<class Types>
58struct 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
77template<class S>
78void 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
98template<class S>
99void 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
117template<class Types>
118Stopwatch::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
154Producer-consumer ping-pong tests. It aims to benchmark condition variables with and without
155thread cancellation support by comparing the time it took to complete the benchmark.
156
157Condition variable with thread cancellation support is boost::condition_variable from
158boost-1.51. Without - std::condition_variable that comes with gcc-4.7.2.
159
160One producer, one to CONSUMER_MAX consumers. The benchmark calls
161condition_variable::notify_all() without holding a mutex to maximize contention within this
162function. Each benchmark for a number of consumers is run three times and the best time is
163picked to get rid of outliers.
164
165The results are reported for each benchmark for a number of consumers. The most important number
166is (std - boost) / std * 100. Positive numbers are when boost::condition_variable is faster,
167negative it is slower.
168
169 */
170
171int 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}