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 | #include "tbb/tick_count.h" |
18 | #include "harness_assert.h" |
19 | |
20 | //! Assert that two times in seconds are very close. |
21 | void AssertNear( double x, double y ) { |
22 | ASSERT( -1.0E-10 <= x-y && x-y <=1.0E-10, NULL ); |
23 | } |
24 | |
25 | //! Test arithmetic operators on tick_count::interval_t |
26 | void TestArithmetic( const tbb::tick_count& t0, const tbb::tick_count& t1, const tbb::tick_count& t2 ) { |
27 | tbb::tick_count::interval_t i= t1-t0; |
28 | tbb::tick_count::interval_t j = t2-t1; |
29 | tbb::tick_count::interval_t k = t2-t0; |
30 | AssertSameType( tbb::tick_count::interval_t(), i-j ); |
31 | AssertSameType( tbb::tick_count::interval_t(), i+j ); |
32 | ASSERT( i.seconds()>1E-9, NULL ); |
33 | ASSERT( j.seconds()>1E-9, NULL ); |
34 | ASSERT( k.seconds()>2E-9, NULL ); |
35 | AssertNear( (i+j).seconds(), k.seconds() ); |
36 | AssertNear( (k-j).seconds(), i.seconds() ); |
37 | AssertNear( ((k-j)+(j-i)).seconds(), k.seconds()-i.seconds() ); |
38 | tbb::tick_count::interval_t sum; |
39 | sum += i; |
40 | sum += j; |
41 | AssertNear( sum.seconds(), k.seconds() ); |
42 | sum -= i; |
43 | AssertNear( sum.seconds(), j.seconds() ); |
44 | sum -= j; |
45 | AssertNear( sum.seconds(), 0.0 ); |
46 | } |
47 | |
48 | //------------------------------------------------------------------------ |
49 | // Test for overhead in calls to tick_count |
50 | //------------------------------------------------------------------------ |
51 | |
52 | //! Wait for given duration. |
53 | /** The duration parameter is in units of seconds. */ |
54 | static void WaitForDuration( double duration ) { |
55 | tbb::tick_count start = tbb::tick_count::now(); |
56 | while( (tbb::tick_count::now()-start).seconds() < duration ) |
57 | continue; |
58 | } |
59 | |
60 | #include "harness.h" |
61 | |
62 | //! Test that average timer overhead is within acceptable limit. |
63 | /** The 'tolerance' value inside the test specifies the limit. */ |
64 | void TestSimpleDelay( int ntrial, double duration, double tolerance ) { |
65 | int error_count = 0; |
66 | double delta = 0; |
67 | // Iteration -1 warms up the code cache. |
68 | for( int trial=-1; trial<ntrial; ++trial ) { |
69 | tbb::tick_count t = tbb::tick_count::now(); |
70 | if( duration ) WaitForDuration(duration); |
71 | delta = (tbb::tick_count::now() - t).seconds() - duration; |
72 | if( trial>=0 && delta > tolerance ) { |
73 | error_count++; |
74 | } |
75 | ASSERT(delta >= 0,"Delta is negative" ); |
76 | } |
77 | ASSERT(error_count < ntrial / 4, "The number of errors exceeded the threshold" ); |
78 | } |
79 | |
80 | //------------------------------------------------------------------------ |
81 | // Test for subtracting calls to tick_count from different threads. |
82 | //------------------------------------------------------------------------ |
83 | |
84 | #include "tbb/atomic.h" |
85 | static tbb::atomic<int> Counter1, Counter2; |
86 | static tbb::atomic<bool> Flag1, Flag2; |
87 | static tbb::tick_count *tick_count_array; |
88 | static double barrier_time; |
89 | |
90 | struct TickCountDifferenceBody { |
91 | TickCountDifferenceBody( int num_threads ) { |
92 | Counter1 = Counter2 = num_threads; |
93 | Flag1 = Flag2 = false; |
94 | } |
95 | void operator()( int id ) const { |
96 | bool last = false; |
97 | // The first barrier. |
98 | if ( --Counter1 == 0 ) last = true; |
99 | while ( !last && !Flag1.load<tbb::acquire>() ) __TBB_Pause( 1 ); |
100 | // Save a time stamp of the first barrier releasing. |
101 | tick_count_array[id] = tbb::tick_count::now(); |
102 | |
103 | // The second barrier. |
104 | if ( --Counter2 == 0 ) Flag2.store<tbb::release>(true); |
105 | // The last thread should release threads from the first barrier after it reaches the second |
106 | // barrier to avoid a deadlock. |
107 | if ( last ) Flag1.store<tbb::release>(true); |
108 | // After the last thread releases threads from the first barrier it waits for a signal from |
109 | // the second barrier. |
110 | while ( !Flag2.load<tbb::acquire>() ) __TBB_Pause( 1 ); |
111 | |
112 | if ( last ) |
113 | // We suppose that the barrier time is a time interval between the moment when the last |
114 | // thread reaches the first barrier and the moment when the same thread is released from |
115 | // the second barrier. This time is not accurate time of two barriers but it is |
116 | // guaranteed that it does not exceed it. |
117 | barrier_time = (tbb::tick_count::now() - tick_count_array[id]).seconds() / 2; |
118 | } |
119 | ~TickCountDifferenceBody() { |
120 | ASSERT( Counter1 == 0 && Counter2 == 0, NULL ); |
121 | } |
122 | }; |
123 | |
124 | //! Test that two tick_count values recorded on different threads can be meaningfully subtracted. |
125 | void TestTickCountDifference( int n ) { |
126 | const double tolerance = 3E-4; |
127 | tick_count_array = new tbb::tick_count[n]; |
128 | |
129 | int num_trials = 0; |
130 | tbb::tick_count start_time = tbb::tick_count::now(); |
131 | do { |
132 | NativeParallelFor( n, TickCountDifferenceBody( n ) ); |
133 | if ( barrier_time > tolerance ) |
134 | // The machine seems to be oversubscribed so skip the test. |
135 | continue; |
136 | for ( int i = 0; i < n; ++i ) { |
137 | for ( int j = 0; j < i; ++j ) { |
138 | double diff = (tick_count_array[i] - tick_count_array[j]).seconds(); |
139 | if ( diff < 0 ) diff = -diff; |
140 | if ( diff > tolerance ) |
141 | REPORT( "Warning: cross-thread tick_count difference = %g > %g = tolerance\n" , diff, tolerance ); |
142 | ASSERT( diff < 3 * tolerance, "Too big difference." ); |
143 | } |
144 | } |
145 | // During 5 seconds we are trying to get 10 successful trials. |
146 | } while ( ++num_trials < 10 && (tbb::tick_count::now() - start_time).seconds() < 5 ); |
147 | REMARK( "Difference test time: %g sec\n" , (tbb::tick_count::now() - start_time).seconds() ); |
148 | // TODO: Find the cause of the machine high load, fix it and upgrade ASSERT_WARNING to ASSERT |
149 | ASSERT_WARNING( num_trials == 10, "The machine seems to be heavily oversubscribed, difference test was skipped." ); |
150 | delete[] tick_count_array; |
151 | } |
152 | |
153 | void TestResolution() { |
154 | static double target_value = 0.314159265358979323846264338327950288419; |
155 | static double step_value = 0.00027182818284590452353602874713526624977572; |
156 | static int range_value = 100; |
157 | double avg_diff = 0.0; |
158 | double max_diff = 0.0; |
159 | for( int i = -range_value; i <= range_value; ++i ) { |
160 | double my_time = target_value + step_value * i; |
161 | tbb::tick_count::interval_t t0(my_time); |
162 | double interval_time = t0.seconds(); |
163 | avg_diff += (my_time - interval_time); |
164 | if ( max_diff < my_time-interval_time) max_diff = my_time-interval_time; |
165 | // time always truncates |
166 | ASSERT(interval_time >= 0 && my_time - interval_time < tbb::tick_count::resolution(), "tick_count resolution out of range" ); |
167 | } |
168 | avg_diff = (avg_diff/(2*range_value+1))/tbb::tick_count::resolution(); |
169 | max_diff /= tbb::tick_count::resolution(); |
170 | REMARK("avg_diff = %g ticks, max_diff = %g ticks\n" , avg_diff, max_diff); |
171 | } |
172 | |
173 | #include "tbb/tbb_thread.h" |
174 | |
175 | int TestMain () { |
176 | // Increased tolerance for Virtual Machines |
177 | double tolerance_multiplier = Harness::GetEnv( "VIRTUAL_MACHINE" ) ? 50. : 1.; |
178 | REMARK( "tolerance_multiplier = %g \n" , tolerance_multiplier ); |
179 | |
180 | tbb::tick_count t0 = tbb::tick_count::now(); |
181 | TestSimpleDelay(/*ntrial=*/1000000,/*duration=*/0, /*tolerance=*/6E-6 * tolerance_multiplier); |
182 | tbb::tick_count t1 = tbb::tick_count::now(); |
183 | TestSimpleDelay(/*ntrial=*/1000, /*duration=*/0.001,/*tolerance=*/15E-6 * tolerance_multiplier); |
184 | tbb::tick_count t2 = tbb::tick_count::now(); |
185 | TestArithmetic(t0,t1,t2); |
186 | |
187 | TestResolution(); |
188 | |
189 | int num_threads = tbb::tbb_thread::hardware_concurrency(); |
190 | ASSERT( num_threads > 0, "tbb::thread::hardware_concurrency() has returned an incorrect value" ); |
191 | if ( num_threads > 1 ) { |
192 | REMARK( "num_threads = %d\n" , num_threads ); |
193 | TestTickCountDifference( num_threads ); |
194 | } else { |
195 | REPORT( "Warning: concurrency is too low for TestTickCountDifference ( num_threads = %d )\n" , num_threads ); |
196 | } |
197 | |
198 | return Harness::Done; |
199 | } |
200 | |