1 /*
2     Copyright (c) 2005-2020 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.
AssertNear(double x,double y)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
TestArithmetic(const tbb::tick_count & t0,const tbb::tick_count & t1,const tbb::tick_count & t2)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. */
WaitForDuration(double duration)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. */
TestSimpleDelay(int ntrial,double duration,double tolerance)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 {
TickCountDifferenceBodyTickCountDifferenceBody91     TickCountDifferenceBody( int num_threads ) {
92         Counter1 = Counter2 = num_threads;
93         Flag1 = Flag2 = false;
94     }
operator ()TickCountDifferenceBody95     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     }
~TickCountDifferenceBodyTickCountDifferenceBody119     ~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.
TestTickCountDifference(int n)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 
TestResolution()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 
TestMain()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