1 /* 2 Copyright (c) 2005-2021 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 "common/test.h" 18 #include "common/utils.h" 19 #include "common/utils_report.h" 20 #include "common/checktype.h" 21 22 #include "oneapi/tbb/detail/_utils.h" 23 24 #include "tbb/enumerable_thread_specific.h" 25 #include "tbb/parallel_for.h" 26 #include "tbb/blocked_range.h" 27 #include "tbb/tbb_allocator.h" 28 #include "tbb/global_control.h" 29 #include "tbb/cache_aligned_allocator.h" 30 31 #include <cstring> 32 #include <cstdio> 33 #include <vector> 34 #include <deque> 35 #include <list> 36 #include <map> 37 #include <utility> 38 #include <atomic> 39 40 //! \file test_enumerable_thread_specific.cpp 41 //! \brief Test for [tls.enumerable_thread_specific] specification 42 43 //! Minimum number of threads 44 static int MinThread = 1; 45 46 //! Maximum number of threads 47 static int MaxThread = 4; 48 49 static std::atomic<int> construction_counter; 50 static std::atomic<int> destruction_counter; 51 52 const int VALID_NUMBER_OF_KEYS = 100; 53 54 //! A minimal class that occupies N bytes. 55 /** Defines default and copy constructor, and allows implicit operator&. Hides operator=. */ 56 template<size_t N = tbb::detail::max_nfs_size> 57 class minimalN: utils::NoAssign { 58 private: 59 int my_value; 60 bool is_constructed; 61 char pad[N-sizeof(int) - sizeof(bool)]; 62 public: 63 minimalN() : utils::NoAssign(), my_value(0) { ++construction_counter; is_constructed = true; } 64 minimalN( const minimalN&m ) : utils::NoAssign(), my_value(m.my_value) { ++construction_counter; is_constructed = true; } 65 ~minimalN() { ++destruction_counter; REQUIRE(is_constructed); is_constructed = false; } 66 void set_value( const int i ) { REQUIRE(is_constructed); my_value = i; } 67 int value( ) const { REQUIRE(is_constructed); return my_value; } 68 }; 69 70 static size_t AlignMask = 0; // set to cache-line-size - 1 71 72 template<typename T> 73 T& check_alignment(T& t, const char *aname) { 74 if( !tbb::detail::is_aligned(&t, AlignMask)) { 75 // TBB_REVAMP_TODO: previously was REPORT_ONCE 76 REPORT("alignment error with %s allocator (%x)\n", aname, (int)size_t(&t) & (AlignMask-1)); 77 } 78 return t; 79 } 80 81 template<typename T> 82 const T& check_alignment(const T& t, const char *aname) { 83 if( !tbb::detail::is_aligned(&t, AlignMask)) { 84 // TBB_REVAMP_TODO: previously was REPORT_ONCE 85 REPORT("alignment error with %s allocator (%x)\n", aname, (int)size_t(&t) & (AlignMask-1)); 86 } 87 return t; 88 } 89 90 // 91 // A helper class that simplifies writing the tests since minimalN does not 92 // define = or + operators. 93 // 94 95 const size_t line_size = tbb::detail::max_nfs_size; 96 97 typedef tbb::enumerable_thread_specific<minimalN<line_size> > flogged_ets; 98 99 class set_body { 100 flogged_ets *a; 101 102 public: 103 set_body( flogged_ets*_a ) : a(_a) { } 104 105 void operator() ( ) const { 106 for (int i = 0; i < VALID_NUMBER_OF_KEYS; ++i) { 107 check_alignment(a[i].local(), "default").set_value(i + 1); 108 } 109 } 110 111 }; 112 113 void do_std_threads( int max_threads, flogged_ets a[] ) { 114 std::vector< std::thread * > threads; 115 116 for (int p = 0; p < max_threads; ++p) { 117 threads.push_back( new std::thread ( set_body( a ) ) ); 118 } 119 120 for (int p = 0; p < max_threads; ++p) { 121 threads[p]->join(); 122 } 123 124 for(int p = 0; p < max_threads; ++p) { 125 delete threads[p]; 126 } 127 } 128 129 void flog_key_creation_and_deletion() { 130 const int FLOG_REPETITIONS = 100; 131 132 for (int p = MinThread; p <= MaxThread; ++p) { 133 for (int j = 0; j < FLOG_REPETITIONS; ++j) { 134 construction_counter = 0; 135 destruction_counter = 0; 136 // causes VALID_NUMBER_OF_KEYS exemplar instances to be constructed 137 flogged_ets* a = new flogged_ets[VALID_NUMBER_OF_KEYS]; 138 REQUIRE(int(construction_counter) == 0); // no exemplars or actual locals have been constructed 139 REQUIRE(int(destruction_counter) == 0); // and none have been destroyed 140 // causes p * VALID_NUMBER_OF_KEYS minimals to be created 141 do_std_threads(p, a); 142 for (int i = 0; i < VALID_NUMBER_OF_KEYS; ++i) { 143 int pcnt = 0; 144 for ( flogged_ets::iterator tli = a[i].begin(); tli != a[i].end(); ++tli ) { 145 REQUIRE( (*tli).value() == i+1 ); 146 ++pcnt; 147 } 148 REQUIRE( pcnt == p); // should be one local per thread. 149 } 150 delete[] a; 151 } 152 REQUIRE( int(construction_counter) == (p)*VALID_NUMBER_OF_KEYS ); 153 REQUIRE( int(destruction_counter) == (p)*VALID_NUMBER_OF_KEYS ); 154 155 construction_counter = 0; 156 destruction_counter = 0; 157 158 // causes VALID_NUMBER_OF_KEYS exemplar instances to be constructed 159 flogged_ets* a = new flogged_ets[VALID_NUMBER_OF_KEYS]; 160 161 for (int j = 0; j < FLOG_REPETITIONS; ++j) { 162 // causes p * VALID_NUMBER_OF_KEYS minimals to be created 163 do_std_threads(p, a); 164 165 for (int i = 0; i < VALID_NUMBER_OF_KEYS; ++i) { 166 for ( flogged_ets::iterator tli = a[i].begin(); tli != a[i].end(); ++tli ) { 167 REQUIRE( (*tli).value() == i+1 ); 168 } 169 a[i].clear(); 170 REQUIRE( static_cast<int>(a[i].end() - a[i].begin()) == 0 ); 171 } 172 } 173 delete[] a; 174 REQUIRE( int(construction_counter) == (FLOG_REPETITIONS*p)*VALID_NUMBER_OF_KEYS ); 175 REQUIRE( int(destruction_counter) == (FLOG_REPETITIONS*p)*VALID_NUMBER_OF_KEYS ); 176 } 177 178 } 179 180 template <typename inner_container> 181 void flog_segmented_interator() { 182 183 bool found_error = false; 184 typedef typename inner_container::value_type T; 185 typedef std::vector< inner_container > nested_vec; 186 inner_container my_inner_container; 187 my_inner_container.clear(); 188 nested_vec my_vec; 189 190 // simple nested vector (neither level empty) 191 const int maxval = 10; 192 for(int i=0; i < maxval; i++) { 193 my_vec.push_back(my_inner_container); 194 for(int j = 0; j < maxval; j++) { 195 my_vec.at(i).push_back((T)(maxval * i + j)); 196 } 197 } 198 199 tbb::detail::d1::segmented_iterator<nested_vec, T> my_si(my_vec); 200 201 T ii; 202 for(my_si=my_vec.begin(), ii=0; my_si != my_vec.end(); ++my_si, ++ii) { 203 if((*my_si) != ii) { 204 found_error = true; 205 } 206 } 207 208 // outer level empty 209 my_vec.clear(); 210 for(my_si=my_vec.begin(); my_si != my_vec.end(); ++my_si) { 211 found_error = true; 212 } 213 214 // inner levels empty 215 my_vec.clear(); 216 for(int i =0; i < maxval; ++i) { 217 my_vec.push_back(my_inner_container); 218 } 219 for(my_si = my_vec.begin(); my_si != my_vec.end(); ++my_si) { 220 found_error = true; 221 } 222 223 // every other inner container is empty 224 my_vec.clear(); 225 for(int i=0; i < maxval; ++i) { 226 my_vec.push_back(my_inner_container); 227 if(i%2) { 228 for(int j = 0; j < maxval; ++j) { 229 my_vec.at(i).push_back((T)(maxval * (i/2) + j)); 230 } 231 } 232 } 233 for(my_si = my_vec.begin(), ii=0; my_si != my_vec.end(); ++my_si, ++ii) { 234 if((*my_si) != ii) { 235 found_error = true; 236 } 237 } 238 239 tbb::detail::d1::segmented_iterator<nested_vec, const T> my_csi(my_vec); 240 for(my_csi=my_vec.begin(), ii=0; my_csi != my_vec.end(); ++my_csi, ++ii) { 241 if((*my_csi) != ii) { 242 found_error = true; 243 } 244 } 245 246 // outer level empty 247 my_vec.clear(); 248 for(my_csi=my_vec.begin(); my_csi != my_vec.end(); ++my_csi) { 249 found_error = true; 250 } 251 252 // inner levels empty 253 my_vec.clear(); 254 for(int i =0; i < maxval; ++i) { 255 my_vec.push_back(my_inner_container); 256 } 257 for(my_csi = my_vec.begin(); my_csi != my_vec.end(); ++my_csi) { 258 found_error = true; 259 } 260 261 // every other inner container is empty 262 my_vec.clear(); 263 for(int i=0; i < maxval; ++i) { 264 my_vec.push_back(my_inner_container); 265 if(i%2) { 266 for(int j = 0; j < maxval; ++j) { 267 my_vec.at(i).push_back((T)(maxval * (i/2) + j)); 268 } 269 } 270 } 271 for(my_csi = my_vec.begin(), ii=0; my_csi != my_vec.end(); ++my_csi, ++ii) { 272 if((*my_csi) != ii) { 273 found_error = true; 274 } 275 } 276 277 278 if(found_error) REPORT("segmented_iterator failed\n"); 279 } 280 281 template <typename Key, typename Val> 282 void flog_segmented_iterator_map() { 283 typedef typename std::map<Key, Val> my_map; 284 typedef std::vector< my_map > nested_vec; 285 my_map my_inner_container; 286 my_inner_container.clear(); 287 nested_vec my_vec; 288 my_vec.clear(); 289 bool found_error = false; 290 291 // simple nested vector (neither level empty) 292 const int maxval = 4; 293 for(int i=0; i < maxval; i++) { 294 my_vec.push_back(my_inner_container); 295 for(int j = 0; j < maxval; j++) { 296 my_vec.at(i).insert(std::make_pair<Key,Val>(maxval * i + j, 2*(maxval*i + j))); 297 } 298 } 299 300 tbb::detail::d1::segmented_iterator<nested_vec, std::pair<const Key, Val> > my_si(my_vec); 301 Key ii; 302 for(my_si=my_vec.begin(), ii=0; my_si != my_vec.end(); ++my_si, ++ii) { 303 if(((*my_si).first != ii) || ((*my_si).second != 2*ii)) { 304 found_error = true; 305 } 306 } 307 308 tbb::detail::d1::segmented_iterator<nested_vec, const std::pair<const Key, Val> > my_csi(my_vec); 309 for(my_csi=my_vec.begin(), ii=0; my_csi != my_vec.end(); ++my_csi, ++ii) { 310 if(((*my_csi).first != ii) || ((*my_csi).second != 2*ii)) { 311 found_error = true; 312 // INFO( "ii=%d, (*my_csi).first=%d, second=%d\n",ii, int((*my_csi).first), int((*my_csi).second)); 313 } 314 } 315 if(found_error) REPORT("segmented_iterator_map failed\n"); 316 } 317 318 void run_segmented_iterator_tests() { 319 // only the following containers can be used with the segmented iterator. 320 flog_segmented_interator<std::vector< int > >(); 321 flog_segmented_interator<std::vector< double > >(); 322 flog_segmented_interator<std::deque< int > >(); 323 flog_segmented_interator<std::deque< double > >(); 324 flog_segmented_interator<std::list< int > >(); 325 flog_segmented_interator<std::list< double > >(); 326 327 flog_segmented_iterator_map<int, int>(); 328 flog_segmented_iterator_map<int, double>(); 329 } 330 331 int align_val(void * const p) { 332 size_t tmp = (size_t)p; 333 int a = 1; 334 while((tmp&0x1) == 0) { a <<=1; tmp >>= 1; } 335 return a; 336 } 337 338 bool is_between(void* lowp, void *highp, void *testp) { 339 if((size_t)lowp < (size_t)testp && (size_t)testp < (size_t)highp) return true; 340 return (size_t)lowp > (size_t)testp && (size_t)testp > (size_t)highp; 341 } 342 343 template<class U> struct alignment_of { 344 typedef struct { char t; U padded; } test_alignment; 345 static const size_t value = sizeof(test_alignment) - sizeof(U); 346 }; 347 using tbb::detail::d1::ets_element; 348 template<typename T, typename OtherType> 349 void allocate_ets_element_on_stack(const char* /* name */) { 350 typedef T aligning_element_type; 351 const size_t my_align = alignment_of<aligning_element_type>::value; 352 OtherType c1; 353 ets_element<aligning_element_type> my_stack_element; 354 OtherType c2; 355 ets_element<aligning_element_type> my_stack_element2; 356 struct { 357 OtherType cxx; 358 ets_element<aligning_element_type> my_struct_element; 359 } mystruct1; 360 tbb::detail::suppress_unused_warning(c1,c2); 361 REQUIRE_MESSAGE(tbb::detail::is_aligned(my_stack_element.value(), my_align), "Error in first stack alignment" ); 362 REQUIRE_MESSAGE(tbb::detail::is_aligned(my_stack_element2.value(), my_align), "Error in second stack alignment" ); 363 REQUIRE_MESSAGE(tbb::detail::is_aligned(mystruct1.my_struct_element.value(), my_align), "Error in struct element alignment" ); 364 } 365 366 class BigType { 367 public: 368 BigType() { /* avoid cl warning C4345 about default initialization of POD types */ } 369 char my_data[12 * 1024 * 1024]; 370 }; 371 372 template<template<class> class Allocator> 373 void TestConstructorWithBigType(const char* allocator_name) { 374 typedef tbb::enumerable_thread_specific<BigType, Allocator<BigType> > CounterBigType; 375 // Test default constructor 376 CounterBigType MyCounters; 377 // Create a local instance. 378 typename CounterBigType::reference my_local = MyCounters.local(); 379 my_local.my_data[0] = 'a'; 380 // Test copy constructor 381 CounterBigType MyCounters2(MyCounters); 382 REQUIRE(check_alignment(MyCounters2.local(), allocator_name).my_data[0]=='a'); 383 } 384 385 size_t init_tbb_alloc_mask() { 386 // TODO: use __TBB_alignof(T) to check for local() results instead of using internal knowledges of ets element padding 387 if(tbb::tbb_allocator<int>::allocator_type() == tbb::tbb_allocator<int>::standard) { 388 // scalable allocator is not available. 389 // INFO("tbb::tbb_allocator is not available\n"); 390 return 1; 391 } 392 else { 393 // this value is for large objects, but will be correct for small. 394 return 64; // TBB_REVAMP_TODO: enable as estimatedCacheLineSize when tbbmalloc is available; 395 } 396 } 397 398 static const size_t cache_allocator_mask = tbb::detail::r1::cache_line_size(); 399 static const size_t tbb_allocator_mask = init_tbb_alloc_mask(); 400 401 //! Test for internal segmented_iterator type, used inside flattened2d class 402 //! \brief \ref error_guessing 403 TEST_CASE("Segmented iterator") { 404 AlignMask = tbb_allocator_mask; 405 run_segmented_iterator_tests(); 406 } 407 408 //! Test ETS keys creation/deletion 409 //! \brief \ref error_guessing \ref boundary 410 TEST_CASE("Key creation and deletion") { 411 AlignMask = tbb_allocator_mask; 412 flog_key_creation_and_deletion(); 413 } 414 415 //! Test construction with big ETS types 416 //! \brief \ref error_guessing 417 TEST_CASE("Constructor with big type") { 418 AlignMask = cache_allocator_mask; 419 TestConstructorWithBigType<tbb::cache_aligned_allocator>("tbb::cache_aligned_allocator"); 420 AlignMask = tbb_allocator_mask; 421 TestConstructorWithBigType<tbb::tbb_allocator>("tbb::tbb_allocator"); 422 } 423 424 //! Test allocation of ETS elements on the stack (internal types) 425 //! \brief \ref error_guessing 426 TEST_CASE("Allocate ETS on stack") { 427 AlignMask = tbb_allocator_mask; 428 allocate_ets_element_on_stack<int,char>("int vs. char"); 429 allocate_ets_element_on_stack<int,short>("int vs. short"); 430 allocate_ets_element_on_stack<int,char[3]>("int vs. char[3]"); 431 allocate_ets_element_on_stack<float,char>("float vs. char"); 432 allocate_ets_element_on_stack<float,short>("float vs. short"); 433 allocate_ets_element_on_stack<float,char[3]>("float vs. char[3]"); 434 } 435 436