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"
28Harness::SpinBarrier sBarrier;
29tbb::critical_section cs;
30const int MAX_WORK = 300;
31
32struct 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
87struct 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
124void
125RunOneCriticalSectionTest(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
195void
196RunParallelTests() {
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
204int TestMain () {
205 if(MinThread <= 0) MinThread = 1;
206
207 if(MaxThread > 0) {
208 RunParallelTests();
209 }
210
211 return Harness::Done;
212}
213