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.
21void 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
26void 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. */
54static 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. */
64void 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"
85static tbb::atomic<int> Counter1, Counter2;
86static tbb::atomic<bool> Flag1, Flag2;
87static tbb::tick_count *tick_count_array;
88static double barrier_time;
89
90struct 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.
125void 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
153void 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
175int 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