1 | /* |
2 | Copyright (c) 2005-2019 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 | // test critical section |
18 | // |
19 | #include "tbb/critical_section.h" |
20 | #include "tbb/task_scheduler_init.h" |
21 | #include "tbb/enumerable_thread_specific.h" |
22 | #include "tbb/tick_count.h" |
23 | #include "harness_assert.h" |
24 | #include "harness.h" |
25 | #include <math.h> |
26 | |
27 | #include "harness_barrier.h" |
28 | Harness::SpinBarrier sBarrier; |
29 | tbb::critical_section cs; |
30 | const int MAX_WORK = 300; |
31 | |
32 | struct BusyBody : NoAssign { |
33 | tbb::enumerable_thread_specific<double> &locals; |
34 | const int nThread; |
35 | const int WorkRatiox100; |
36 | int &unprotected_count; |
37 | bool test_throw; |
38 | |
39 | BusyBody( int nThread_, int workRatiox100_, tbb::enumerable_thread_specific<double> &locals_, int &unprotected_count_, bool test_throw_) : |
40 | locals(locals_), |
41 | nThread(nThread_), |
42 | WorkRatiox100(workRatiox100_), |
43 | unprotected_count(unprotected_count_), |
44 | test_throw(test_throw_) { |
45 | sBarrier.initialize(nThread_); |
46 | } |
47 | |
48 | void operator()(const int /* threadID */ ) const { |
49 | int nIters = MAX_WORK/nThread; |
50 | sBarrier.wait(); |
51 | tbb::tick_count t0 = tbb::tick_count::now(); |
52 | for(int j = 0; j < nIters; j++) { |
53 | |
54 | for(int i = 0; i < MAX_WORK * (100 - WorkRatiox100); i++) { |
55 | locals.local() += 1.0; |
56 | } |
57 | cs.lock(); |
58 | ASSERT( !cs.try_lock(), "recursive try_lock must fail" ); |
59 | #if TBB_USE_EXCEPTIONS && !__TBB_THROW_ACROSS_MODULE_BOUNDARY_BROKEN |
60 | if(test_throw && j == (nIters / 2)) { |
61 | bool was_caught = false, |
62 | unknown_exception = false; |
63 | try { |
64 | cs.lock(); |
65 | } |
66 | catch(tbb::improper_lock& e) { |
67 | ASSERT( e.what(), "Error message is absent" ); |
68 | was_caught = true; |
69 | } |
70 | catch(...) { |
71 | was_caught = unknown_exception = true; |
72 | } |
73 | ASSERT(was_caught, "Recursive lock attempt did not throw" ); |
74 | ASSERT(!unknown_exception, "tbb::improper_lock exception is expected" ); |
75 | } |
76 | #endif /* TBB_USE_EXCEPTIONS && !__TBB_THROW_ACROSS_MODULE_BOUNDARY_BROKEN */ |
77 | for(int i = 0; i < MAX_WORK * WorkRatiox100; i++) { |
78 | locals.local() += 1.0; |
79 | } |
80 | unprotected_count++; |
81 | cs.unlock(); |
82 | } |
83 | locals.local() = (tbb::tick_count::now() - t0).seconds(); |
84 | } |
85 | }; |
86 | |
87 | struct BusyBodyScoped : NoAssign { |
88 | tbb::enumerable_thread_specific<double> &locals; |
89 | const int nThread; |
90 | const int WorkRatiox100; |
91 | int &unprotected_count; |
92 | bool test_throw; |
93 | |
94 | BusyBodyScoped( int nThread_, int workRatiox100_, tbb::enumerable_thread_specific<double> &locals_, int &unprotected_count_, bool test_throw_) : |
95 | locals(locals_), |
96 | nThread(nThread_), |
97 | WorkRatiox100(workRatiox100_), |
98 | unprotected_count(unprotected_count_), |
99 | test_throw(test_throw_) { |
100 | sBarrier.initialize(nThread_); |
101 | } |
102 | |
103 | void operator()(const int /* threadID */ ) const { |
104 | int nIters = MAX_WORK/nThread; |
105 | sBarrier.wait(); |
106 | tbb::tick_count t0 = tbb::tick_count::now(); |
107 | for(int j = 0; j < nIters; j++) { |
108 | |
109 | for(int i = 0; i < MAX_WORK * (100 - WorkRatiox100); i++) { |
110 | locals.local() += 1.0; |
111 | } |
112 | { |
113 | tbb::critical_section::scoped_lock my_lock(cs); |
114 | for(int i = 0; i < MAX_WORK * WorkRatiox100; i++) { |
115 | locals.local() += 1.0; |
116 | } |
117 | unprotected_count++; |
118 | } |
119 | } |
120 | locals.local() = (tbb::tick_count::now() - t0).seconds(); |
121 | } |
122 | }; |
123 | |
124 | void |
125 | RunOneCriticalSectionTest(int nThreads, int csWorkRatio, bool test_throw) { |
126 | tbb::task_scheduler_init init(tbb::task_scheduler_init::deferred); |
127 | tbb::enumerable_thread_specific<double> test_locals; |
128 | int myCount = 0; |
129 | BusyBody myBody(nThreads, csWorkRatio, test_locals, myCount, test_throw); |
130 | BusyBodyScoped myScopedBody(nThreads, csWorkRatio, test_locals, myCount, test_throw); |
131 | init.initialize(nThreads); |
132 | tbb::tick_count t0; |
133 | { |
134 | t0 = tbb::tick_count::now(); |
135 | myCount = 0; |
136 | NativeParallelFor(nThreads, myBody); |
137 | ASSERT(myCount == (MAX_WORK - (MAX_WORK % nThreads)), NULL); |
138 | REMARK("%d threads, work ratio %d per cent, time %g" , nThreads, csWorkRatio, (tbb::tick_count::now() - t0).seconds()); |
139 | if (nThreads > 1) { |
140 | double etsSum = 0; |
141 | double etsMax = 0; |
142 | double etsMin = 0; |
143 | double etsSigmaSq = 0; |
144 | double etsSigma = 0; |
145 | |
146 | for(tbb::enumerable_thread_specific<double>::const_iterator ci = test_locals.begin(); ci != test_locals.end(); ci++) { |
147 | etsSum += *ci; |
148 | if(etsMax==0.0) { |
149 | etsMin = *ci; |
150 | } |
151 | else { |
152 | if(etsMin > *ci) etsMin = *ci; |
153 | } |
154 | if(etsMax < *ci) etsMax = *ci; |
155 | } |
156 | double etsAvg = etsSum / (double)nThreads; |
157 | for(tbb::enumerable_thread_specific<double>::const_iterator ci = test_locals.begin(); ci != test_locals.end(); ci++) { |
158 | etsSigma = etsAvg - *ci; |
159 | etsSigmaSq += etsSigma * etsSigma; |
160 | } |
161 | // an attempt to gauge the "fairness" of the scheduling of the threads. We figure |
162 | // the standard deviation, and compare it with the maximum deviation from the |
163 | // average time. If the difference is 0 that means all threads finished in the same |
164 | // amount of time. If non-zero, the difference is divided by the time, and the |
165 | // negative log is taken. If > 2, then the difference is on the order of 0.01*t |
166 | // where T is the average time. We aritrarily define this as "fair." |
167 | etsSigma = sqrt(etsSigmaSq/double(nThreads)); |
168 | etsMax -= etsAvg; // max - a == delta1 |
169 | etsMin = etsAvg - etsMin; // a - min == delta2 |
170 | if(etsMax < etsMin) etsMax = etsMin; |
171 | etsMax -= etsSigma; |
172 | // ASSERT(etsMax >= 0, NULL); // shouldn't the maximum difference from the mean be > the stddev? |
173 | etsMax = (etsMax > 0.0) ? etsMax : 0.0; // possible rounding error |
174 | double fairness = etsMax / etsAvg; |
175 | if(fairness == 0.0) { |
176 | fairness = 100.0; |
177 | } |
178 | else fairness = - log10(fairness); |
179 | if(fairness > 2.0 ) { |
180 | REMARK(" Fair (%g)\n" , fairness); |
181 | } |
182 | else { |
183 | REMARK(" Unfair (%g)\n" , fairness); |
184 | } |
185 | } |
186 | myCount = 0; |
187 | NativeParallelFor(nThreads, myScopedBody); |
188 | ASSERT(myCount == (MAX_WORK - (MAX_WORK % nThreads)), NULL); |
189 | |
190 | } |
191 | |
192 | init.terminate(); |
193 | } |
194 | |
195 | void |
196 | RunParallelTests() { |
197 | for(int p = MinThread; p <= MaxThread; p++) { |
198 | for(int cs_ratio = 1; cs_ratio < 95; cs_ratio *= 2) { |
199 | RunOneCriticalSectionTest(p, cs_ratio, /*test_throw*/true); |
200 | } |
201 | } |
202 | } |
203 | |
204 | int TestMain () { |
205 | if(MinThread <= 0) MinThread = 1; |
206 | |
207 | if(MaxThread > 0) { |
208 | RunParallelTests(); |
209 | } |
210 | |
211 | return Harness::Done; |
212 | } |
213 | |