1 /*
2 Copyright (c) 2005-2022 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 #if __INTEL_COMPILER && _MSC_VER
18 #pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
19 #endif
20
21 #include <common/test.h>
22 #include <common/spin_barrier.h>
23 #include <common/state_trackable.h>
24 #include <common/container_move_support.h>
25 #include <common/range_based_for_support.h>
26 #include <common/utils.h>
27 #include <common/utils_concurrency_limit.h>
28 #include <common/vector_types.h>
29 #include <common/concepts_common.h>
30 #include <tbb/concurrent_vector.h>
31 #include <tbb/tick_count.h>
32 #include <tbb/parallel_reduce.h>
33 #include <tbb/parallel_for.h>
34 #include <algorithm>
35 #include <cmath>
36
37 //! \file test_concurrent_vector.cpp
38 //! \brief Test for [containers.concurrent_vector] specification
39
TestSort()40 void TestSort() {
41 for( int n=0; n<100; n=n*3+1 ) {
42 tbb::concurrent_vector<int> array(n);
43 for( int i=0; i<n; ++i ){
44 array.at(i) = (i*7)%n;
45 }
46 std::sort( array.begin(), array.end() );
47 for( int i=0; i<n; ++i ){
48 REQUIRE( array[i]==i );
49 }
50 }
51 }
52
TestRangeBasedFor()53 void TestRangeBasedFor(){
54 using namespace range_based_for_support_tests;
55
56 using c_vector = tbb::concurrent_vector<int>;
57 c_vector a_c_vector;
58
59 const int sequence_length = 10;
60 for (int i = 1; i<= sequence_length; ++i){
61 a_c_vector.push_back(i);
62 }
63
64 REQUIRE_MESSAGE( range_based_for_accumulate(a_c_vector, std::plus<int>(), 0) == gauss_summ_of_int_sequence(sequence_length), "incorrect accumulated value generated via range based for ?");
65 }
66
67 struct default_container_traits {
68 template <typename container_type, typename iterator_type>
construct_containerdefault_container_traits69 static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end){
70 container_type* ptr = reinterpret_cast<container_type*>(&storage);
71 new (ptr) container_type(begin, end);
72 return *ptr;
73 }
74
75 template <typename container_type, typename iterator_type, typename allocator_type>
construct_containerdefault_container_traits76 static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end, allocator_type const& a){
77 container_type* ptr = reinterpret_cast<container_type*>(&storage);
78 new (ptr) container_type(begin, end, a);
79 return *ptr;
80 }
81 };
82
83 struct c_vector_type : default_container_traits {
84 template <typename T, typename Allocator>
85 using container_type = tbb::concurrent_vector<T, Allocator>;
86
87 template <typename T>
88 using container_value_type = T;
89
90 using init_iterator_type = move_support_tests::FooIterator;
91 template<typename element_type, typename allocator_type>
92 struct apply{
93 using type = tbb::concurrent_vector<element_type, allocator_type >;
94 };
95
96 enum{ expected_number_of_items_to_allocate_for_steal_move = 0 };
97
98 template<typename element_type, typename allocator_type, typename iterator>
equalc_vector_type99 static bool equal(tbb::concurrent_vector<element_type, allocator_type > const& c, iterator begin, iterator end){
100 bool equal_sizes = (size_t)std::distance(begin, end) == c.size();
101 return equal_sizes && std::equal(c.begin(), c.end(), begin);
102 }
103 };
104
TestSerialGrowByWithMoveIterators()105 void TestSerialGrowByWithMoveIterators(){
106 using fixture_t = move_support_tests::DefaultStatefulFixtureHelper<c_vector_type>::type;
107 using vector_t = fixture_t::container_type;
108
109 fixture_t fixture;
110
111 vector_t dst(fixture.dst_allocator);
112 dst.grow_by(std::make_move_iterator(fixture.source.begin()), std::make_move_iterator(fixture.source.end()));
113
114 fixture.verify_content_deep_moved(dst);
115 }
116
117 #if HAVE_m128 || HAVE_m256
118
119 template<typename ClassWithVectorType>
TestVectorTypes()120 void TestVectorTypes() {
121 tbb::concurrent_vector<ClassWithVectorType> v;
122 for( int i = 0; i < 100; ++i ) {
123 // VC8 does not properly align a temporary value; to work around, use explicit variable
124 ClassWithVectorType foo(i);
125 v.push_back(foo);
126 for( int j=0; j<i; ++j ) {
127 ClassWithVectorType bar(j);
128 REQUIRE( v[j]==bar );
129 }
130 }
131 }
132 #endif /* HAVE_m128 | HAVE_m256 */
133
134
135 static tbb::concurrent_vector<std::size_t> Primes;
136
137 class FindPrimes {
is_prime(std::size_t val) const138 bool is_prime( std::size_t val ) const {
139 int limit, factor = 3;
140 if( val<5u )
141 return val==2;
142 else {
143 limit = long(sqrtf(float(val))+0.5f);
144 while( factor<=limit && val % factor )
145 ++factor;
146 return factor>limit;
147 }
148 }
149 public:
operator ()(const std::size_t idx) const150 void operator()( const std::size_t idx ) const {
151 if( idx % 2 && is_prime(idx) ) {
152 Primes.push_back( idx );
153 }
154 }
155 };
156
TimeFindPrimes(std::size_t nthread)157 double TimeFindPrimes( std::size_t nthread ) {
158 Primes.clear();
159 const std::size_t count = 1048576;
160 Primes.reserve(count);// TODO: or compact()?
161 tbb::tick_count t0 = tbb::tick_count::now();
162 std::size_t block_size = count / nthread;
163 utils::NativeParallelFor(count, block_size, FindPrimes() );
164 tbb::tick_count t1 = tbb::tick_count::now();
165 return (t1-t0).seconds();
166 }
167
TestFindPrimes()168 void TestFindPrimes() {
169 // Time fully subscribed run.
170
171 // TimeFindPrimes( tbb::task_scheduler_init::automatic );
172 double t2 = TimeFindPrimes( utils::get_platform_max_threads() );
173
174 // Time parallel run that is very likely oversubscribed.
175 #if TBB_TEST_LOW_WORKLOAD
176 double tx = TimeFindPrimes(32);
177 #else
178 double tx = TimeFindPrimes(128);
179 #endif
180 INFO("TestFindPrimes: t2 == " << t2 << " tx == " << tx << "k == " << tx / t2);
181
182 // We allow the X-thread run a little extra time to allow for thread overhead.
183 // Theoretically, following test will fail on machine with >X processors.
184 // But that situation is not going to come up in the near future,
185 // and the generalization to fix the issue is not worth the trouble.
186 WARN_MESSAGE( tx <= 1.3*t2, "Warning: grow_by is pathetically slow");
187 INFO("t2 == " << t2 << " tx == " << tx << "k == " << tx / t2);
188 }
189
190 template <typename Type, typename Allocator>
191 class test_grow_by_and_resize {
192 tbb::concurrent_vector<Type, Allocator> &my_c;
193 public:
test_grow_by_and_resize(tbb::concurrent_vector<Type,Allocator> & c)194 test_grow_by_and_resize( tbb::concurrent_vector<Type, Allocator> &c ) : my_c(c) {}
operator ()() const195 void operator()() const {
196 const typename tbb::concurrent_vector<Type, Allocator>::size_type sz = my_c.size();
197 my_c.grow_by( 5 );
198 REQUIRE( my_c.size() == sz + 5 );
199 my_c.resize( sz );
200 REQUIRE( my_c.size() == sz );
201 }
202 };
203
test_scoped_allocator()204 void test_scoped_allocator() {
205 using allocator_data_type = AllocatorAwareData<std::scoped_allocator_adaptor<std::allocator<int>>>;
206 using allocator_type = std::scoped_allocator_adaptor<std::allocator<allocator_data_type>>;
207 using container_type = tbb::concurrent_vector<allocator_data_type, allocator_type>;
208
209 allocator_type allocator;
210 allocator_data_type data1(1, allocator);
211 allocator_data_type data2(2, allocator);
212
213 auto init_list = {data1, data2};
214
215 container_type c1(allocator), c2(allocator);
216
217 allocator_data_type::activate();
218
219 c1.grow_by(100);
220 c1.grow_by(10, data1);
221 c1.grow_by(init_list.begin(), init_list.end());
222 c1.grow_by(init_list);
223
224 c1.clear();
225
226 c1.grow_to_at_least(100);
227 c1.grow_to_at_least(110, data1);
228
229 c1.clear();
230
231 c1.push_back(data1);
232 c1.push_back(data2);
233 c1.push_back(std::move(data1));
234 c1.emplace_back(1);
235
236 c1.clear();
237
238 c1.reserve(100);
239 c1.resize(110);
240 c1.resize(100);
241 c1.resize(110, data1);
242 c1.resize(100, data1);
243
244 c1.shrink_to_fit();
245
246 c1.clear();
247
248 c1.grow_by(10, data1);
249 c2.grow_by(20, data2);
250
251 c1 = c2;
252 c2 = std::move(c1);
253
254 allocator_data_type::deactivate();
255 }
256
257 template <bool default_construction_present> struct do_default_construction_test {
operator ()do_default_construction_test258 template<typename FuncType> void operator() ( FuncType func ) const { func(); }
259 };
260 template <> struct do_default_construction_test<false> {
operator ()do_default_construction_test261 template<typename FuncType> void operator()( FuncType ) const {}
262 };
263
264 template <typename Type, typename Allocator>
CompareVectors(const tbb::concurrent_vector<Type,Allocator> & c1,const tbb::concurrent_vector<Type,Allocator> & c2)265 void CompareVectors( const tbb::concurrent_vector<Type, Allocator> &c1, const tbb::concurrent_vector<Type, Allocator> &c2 ) {
266 REQUIRE( (!(c1 == c2) && c1 != c2) );
267 REQUIRE( (c1 <= c2 && c1 < c2 && c2 >= c1 && c2 > c1) );
268 }
269
270 template <typename Type, typename Allocator>
CompareVectors(const tbb::concurrent_vector<std::weak_ptr<Type>,Allocator> &,const tbb::concurrent_vector<std::weak_ptr<Type>,Allocator> &)271 void CompareVectors( const tbb::concurrent_vector<std::weak_ptr<Type>, Allocator> &, const tbb::concurrent_vector<std::weak_ptr<Type>, Allocator> & ) {
272 /* do nothing for std::weak_ptr */
273 }
274
275 template <bool default_construction_present, typename Type, typename Allocator>
Examine(tbb::concurrent_vector<Type,Allocator> c,const std::vector<Type> & vec)276 void Examine( tbb::concurrent_vector<Type, Allocator> c, const std::vector<Type> &vec ) {
277 using vector_t = tbb::concurrent_vector<Type, Allocator>;
278 using size_type_t = typename vector_t::size_type;
279
280
281 REQUIRE( c.size() == vec.size() );
282 for ( size_type_t i=0; i<c.size(); ++i ) {
283 REQUIRE( utils::IsEqual()(c[i], vec[i]) );
284 }
285 do_default_construction_test<default_construction_present>()(test_grow_by_and_resize<Type,Allocator>(c));
286 c.grow_by( size_type_t(5), c[0] );
287 c.grow_to_at_least( c.size()+5, c.at(0) );
288 vector_t c2;
289 c2.reserve( 5 );
290 std::copy( c.begin(), c.begin() + 5, std::back_inserter( c2 ) );
291
292 c.grow_by( c2.begin(), c2.end() );
293 const vector_t& cvcr = c;
294 REQUIRE( utils::IsEqual()(cvcr.front(), *(c2.rend()-1)) );
295 REQUIRE( utils::IsEqual()(cvcr.back(), *c2.rbegin()));
296 REQUIRE( utils::IsEqual()(*c.cbegin(), *(c.crend()-1)) );
297 REQUIRE( utils::IsEqual()(*(c.cend()-1), *c.crbegin()) );
298 c.swap( c2 );
299 REQUIRE( c.size() == 5 );
300 CompareVectors( c, c2 );
301 c.swap( c2 );
302 c2.clear();
303 REQUIRE( c2.size() == 0 );
304 c2.shrink_to_fit();
305 Allocator a = c.get_allocator();
306 a.deallocate( a.allocate(1), 1 );
307 }
308
309 template <typename Type>
310 class test_default_construction {
311 const std::vector<Type> &my_vec;
312 public:
test_default_construction(const std::vector<Type> & vec)313 test_default_construction( const std::vector<Type> &vec ) : my_vec(vec) {}
operator ()() const314 void operator()() const {
315 // Construction with initial size specified by argument n.
316 tbb::concurrent_vector<Type> c7( my_vec.size() );
317 std::copy( my_vec.begin(), my_vec.end(), c7.begin() );
318 Examine</*default_construction_present = */true>( c7, my_vec );
319 tbb::concurrent_vector< Type, std::allocator<Type> > c8( my_vec.size() );
320 std::copy( c7.begin(), c7.end(), c8.begin() );
321 Examine</*default_construction_present = */true>( c8, my_vec );
322 }
323 };
324
325 template <bool default_construction_present, typename Type>
TypeTester(const std::vector<Type> & vec)326 void TypeTester( const std::vector<Type> &vec ) {
327 __TBB_ASSERT( vec.size() >= 5, "Array should have at least 5 elements" );
328 // Construct empty vector.
329 tbb::concurrent_vector<Type> c1;
330 std::copy( vec.begin(), vec.end(), std::back_inserter(c1) );
331 Examine<default_construction_present>( c1, vec );
332 // Constructor from initializer_list.
333 tbb::concurrent_vector<Type> c2({vec[0],vec[1],vec[2]});
334 std::copy( vec.begin()+3, vec.end(), std::back_inserter(c2) );
335 Examine<default_construction_present>( c2, vec );
336 // Copying constructor.
337 tbb::concurrent_vector<Type> c3(c1);
338 Examine<default_construction_present>( c3, vec );
339 // Construct with non-default allocator
340 tbb::concurrent_vector< Type, std::allocator<Type> > c4;
341 std::copy( vec.begin(), vec.end(), std::back_inserter(c4) );
342 Examine<default_construction_present>( c4, vec );
343 // Construction with initial size specified by argument n.
344 do_default_construction_test<default_construction_present>()(test_default_construction<Type>(vec));
345 // Construction with initial size specified by argument n, initialization by copying of t, and given allocator instance.
346 std::allocator<Type> allocator;
347 tbb::concurrent_vector< Type, std::allocator<Type> > c9(vec.size(), vec[1], allocator);
348 Examine<default_construction_present>( c9, std::vector<Type>(vec.size(), vec[1]) );
349 // Construction with copying iteration range and given allocator instance.
350 tbb::concurrent_vector< Type, std::allocator<Type> > c10(c1.begin(), c1.end(), allocator);
351 Examine<default_construction_present>( c10, vec );
352 tbb::concurrent_vector<Type> c11(vec.begin(), vec.end());
353 Examine<default_construction_present>( c11, vec );
354 }
355
TestTypes()356 void TestTypes() {
357 const int NUMBER = 100;
358
359 std::vector<int> intArr;
360 for ( int i=0; i<NUMBER; ++i ) intArr.push_back(i);
361 TypeTester</*default_construction_present = */true>( intArr );
362
363 std::vector< std::reference_wrapper<int> > refArr;
364 // The constructor of std::reference_wrapper<T> from T& is explicit in some versions of libstdc++.
365 for ( int i=0; i<NUMBER; ++i ) refArr.push_back( std::reference_wrapper<int>(intArr[i]) );
366 TypeTester</*default_construction_present = */false>( refArr );
367
368 // std::vector< std::atomic<int> > tbbIntArr( NUMBER ); //TODO compilation error
369 // for ( int i=0; i<NUMBER; ++i ) tbbIntArr[i] = i;
370 // TypeTester</*default_construction_present = */true>( tbbIntArr );
371
372 std::vector< std::shared_ptr<int> > shrPtrArr;
373 for ( int i=0; i<NUMBER; ++i ) shrPtrArr.push_back( std::make_shared<int>(i) );
374 TypeTester</*default_construction_present = */true>( shrPtrArr );
375
376 std::vector< std::weak_ptr<int> > wkPtrArr;
377 std::copy( shrPtrArr.begin(), shrPtrArr.end(), std::back_inserter(wkPtrArr) );
378 TypeTester</*default_construction_present = */true>( wkPtrArr );
379 }
380
381 template <typename Vector>
test_grow_by_empty_range(Vector & v,typename Vector::value_type * range_begin_end)382 void test_grow_by_empty_range( Vector &v, typename Vector::value_type* range_begin_end ) {
383 const Vector v_copy = v;
384 REQUIRE_MESSAGE( (v.grow_by( range_begin_end, range_begin_end ) == v.end()), "grow_by(empty_range) returned a wrong iterator." );
385 REQUIRE_MESSAGE( v == v_copy, "grow_by(empty_range) has changed the vector." );
386 }
387
TestSerialGrowByRange(bool fragmented_vector)388 void TestSerialGrowByRange( bool fragmented_vector ) {
389 tbb::concurrent_vector<int> v;
390 if ( fragmented_vector ) {
391 v.reserve( 1 );
392 }
393 int init_range[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
394 REQUIRE_MESSAGE( (v.grow_by( init_range, init_range + (utils::array_length( init_range )) ) == v.begin()), "grow_by(I,I) returned a wrong iterator." );
395 REQUIRE_MESSAGE( std::equal( v.begin(), v.end(), init_range ), "grow_by(I,I) did not properly copied all elements ?" );
396 test_grow_by_empty_range( v, init_range );
397 test_grow_by_empty_range( v, (int*)nullptr );
398 }
399
400 template <typename allocator_type>
TestConcurrentOperationsWithUnSafeOperations(std::size_t threads_number)401 void TestConcurrentOperationsWithUnSafeOperations(std::size_t threads_number) {
402 using vector_type = tbb::concurrent_vector<move_support_tests::Foo, allocator_type>;
403
404 vector_type vector;
405
406 constexpr std::size_t max_operations = 1000;
407 std::atomic<int> curr_unsafe_thread{-1};
408 // 0 - is safe operations
409 // 1 - is shrink_to_fit
410 // 2 - is clear + shrink_to_fit
411 // 3 - is resize
412 std::vector<std::size_t> operations(std::size_t(max_operations * 0.95), 0);
413 utils::FastRandom<> op_rand(42);
414 for (std::size_t i = std::size_t(max_operations * 0.95); i < max_operations; ++i) {
415 std::size_t random_operation = op_rand.get() % 3;
416 operations.push_back(random_operation + 1);
417 }
418
419 // Array of active threads
420 std::unique_ptr<std::atomic<int>[]> active_threads{ new std::atomic<int>[threads_number]() };
421 // If thread still have i < max_operations than in array will be false
422 // When some thread finish it operation, set true in active_thread on thread_id position and start executing only safe operations
423 // Than wait all threads
424 // When all threads is finish their operations, all thread exit from lambda
425 auto all_done = [&active_threads, threads_number] {
426 for (std::size_t i = 0; i < threads_number; ++i) {
427 if (active_threads[i].load(std::memory_order_relaxed) == 0) return false;
428 }
429 return true;
430 };
431
432 // Need double synchronization to correct work
433 std::unique_ptr<std::atomic<int>[]> ready_threads{ new std::atomic<int>[threads_number]() };
434 auto all_ready_leave = [&ready_threads, threads_number] {
435 for (std::size_t i = 0; i < threads_number; ++i) {
436 if (ready_threads[i].load(std::memory_order_relaxed) == 0) return false;
437 }
438 return true;
439 };
440
441 utils::SpinBarrier barrier(threads_number);
442 auto concurrent_func = [&operations, &vector, &curr_unsafe_thread, &barrier, &all_done, &active_threads,
443 &all_ready_leave, &ready_threads] (std::size_t thread_id)
444 {
445 std::vector<std::size_t> local_operations(operations);
446 utils::FastRandom<> rand(thread_id);
447 // std::shuffle doesn't work with msvc2017 and FastRandom
448 for (std::size_t i = local_operations.size(); i > 1; --i) {
449 std::size_t j = rand.get() % i;
450 std::swap(local_operations[i - 1], local_operations[j]);
451 }
452
453 std::size_t i = 0;
454 do {
455 if (all_done()) ready_threads[thread_id] = 1;
456 if (curr_unsafe_thread.load() != -1) {
457 // If lock taken, wait
458 // First wait unblock unsafe thread
459 // Second wait finish unsafe operations
460 barrier.wait();
461 barrier.wait();
462 }
463 // Is safe operation
464 if (active_threads[thread_id] == 1 || local_operations[i] == 0) {
465 // If lock is free, perform various operations
466 std::size_t random_operation = rand.get() % 3;
467 switch (random_operation) {
468 case 0:
469 {
470 vector.push_back(1);
471 }
472 break;
473 case 1:
474 {
475 std::size_t grow_size = rand.get() % 100;
476 vector.grow_by(grow_size, 1);
477 }
478 break;
479 case 2:
480 {
481 std::size_t grow_at_least_size = vector.size() + rand.get() % 100;
482 vector.grow_to_at_least(grow_at_least_size, 1);
483 }
484 break;
485 }
486 } else {
487 int default_unsafe_thread = -1;
488 if (curr_unsafe_thread.compare_exchange_strong(default_unsafe_thread, int(thread_id))) {
489 barrier.wait();
490 // All threads are blocked we can execute our unsafe operation
491 switch (local_operations[i]) {
492 case 1:
493 vector.shrink_to_fit();
494 break;
495 case 2:
496 {
497 vector.clear();
498 vector.shrink_to_fit();
499 }
500 break;
501 case 3:
502 {
503 vector.resize(0);
504 }
505 break;
506 }
507 curr_unsafe_thread = -1;
508 barrier.wait();
509 }
510 }
511 ++i;
512 if (i >= local_operations.size()) active_threads[thread_id] = 1;
513 } while (!all_ready_leave() || !all_done());
514 };
515
516 utils::NativeParallelFor(threads_number, concurrent_func);
517
518 vector.clear();
519 vector.shrink_to_fit();
520 }
521
522 template <typename RangeType>
reduce_vector(RangeType test_range)523 int reduce_vector(RangeType test_range) {
524 return tbb::parallel_reduce(test_range, 0,
525 [] ( const RangeType& range, int sum ) {
526 for (auto it = range.begin(); it != range.end(); ++it) {
527 sum += *it;
528 }
529
530 return sum;
531 },
532 [] ( const int& lhs, const int& rhs) {
533 return lhs + rhs;
534 }
535 );
536 }
537
538 //! Test the grow_by on range
539 //! \brief \ref interface \ref requirement
540 TEST_CASE("testing serial grow_by range"){
541 TestSerialGrowByRange(/*fragmented_vector = */false);
542 TestSerialGrowByRange(/*fragmented_vector = */true);
543 }
544
545 //! Test of push_back operation
546 //! \brief \ref interface
547 TEST_CASE("testing range based for support"){
548 TestRangeBasedFor();
549 }
550
551 //! Test of work STL algorithms with concurrent_vector iterator.
552 //! \brief \ref interface
553 TEST_CASE("testing sort"){
554 TestSort();
555 }
556
557 //! Test concurrent_vector with vector types
558 //! \brief \ref error_guessing
559 TEST_CASE("testing concurrent_vector with vector types"){
560 #if HAVE_m128
561 TestVectorTypes<ClassWithSSE>();
562 #endif
563 #if HAVE_m256
564 if (have_AVX()) TestVectorTypes<ClassWithAVX>();
565 #endif
566 }
567
568 //! Test concurrent push_back operation
569 //! \brief \ref error_guessing
570 TEST_CASE("testing find primes"){
571 TestFindPrimes();
572 }
573
574 //! Test concurrent_vector with std::scoped_allocator_adaptor
575 //! \brief \ref error_guessing
576 TEST_CASE("test concurrent_vector with std::scoped_allocator_adaptor") {
577 test_scoped_allocator();
578 }
579
580 //! Test type of vector
581 //! \brief \ref requirement
582 TEST_CASE("testing types"){
583 TestTypes();
584 }
585
586 //! Test concurrent and unsafe operations
587 //! \brief \ref regression \ref error_guessing
588 TEST_CASE("Work without hang") {
589 using allocator_type = StaticSharedCountingAllocator<std::allocator<move_support_tests::Foo>>;
590 std::size_t max_threads = utils::get_platform_max_threads() - 1;
591
592 for (std::size_t threads = 1; threads < max_threads; threads = std::size_t(double(threads) * 2.7)) {
593 allocator_type::init_counters();
594 TestConcurrentOperationsWithUnSafeOperations<allocator_type>(threads);
595
596 REQUIRE( allocator_type::allocations == allocator_type::frees );
597 REQUIRE( allocator_type::items_allocated == allocator_type::items_freed );
598 REQUIRE( allocator_type::items_constructed == allocator_type::items_destroyed );
599 }
600 }
601
602 #if TBB_USE_EXCEPTIONS
603 //! Whitebox test for segment table extension
604 //! \brief \ref regression \ref error_guessing
605 TEST_CASE("Whitebox test for segment table extension") {
606 using allocator_type = StaticSharedCountingAllocator<std::allocator<move_support_tests::Foo>>;
607 using vector_type = tbb::concurrent_vector<move_support_tests::Foo, allocator_type>;
608
609 std::size_t max_number_of_elements_in_embedded = 12;
610
611 for (std::size_t i = 3; i < max_number_of_elements_in_embedded; i += 3) {
612 vector_type vector;
613 allocator_type::init_counters();
614 allocator_type::set_limits(std::size_t(1) << (i + 1));
615
616 try {
617 for (std::size_t j = 0; j < std::size_t(1) << i; ++j) {
618 vector.push_back(1);
619 }
620 vector.grow_by(1000);
621 } catch (std::bad_alloc& ) {
622 allocator_type::set_limits();
623 vector_type copy_of_vector(vector);
624 vector_type copy_of_copy(copy_of_vector);
625 vector_type assigned_vector;
626 assigned_vector = vector;
627 REQUIRE(copy_of_vector == copy_of_copy);
628 REQUIRE(assigned_vector == copy_of_copy);
629 }
630 }
631 }
632
633 //! Test exception in constructors
634 //! \brief \ref regression \ref error_guessing
635 TEST_CASE("Test exception in constructors") {
636 using allocator_type = StaticSharedCountingAllocator<std::allocator<double>>;
637 using vector_type = tbb::concurrent_vector<double, allocator_type>;
638
639 allocator_type::set_limits(1);
640
__anon5f703c9f0702null641 REQUIRE_THROWS_AS( [] {
642 vector_type vec1(42, 42.);
643 utils::suppress_unused_warning(vec1);
644 }(), const std::bad_alloc);
645
646 auto list = { 42., 42., 42., 42., 42., 42., 42., 42., 42., 42. };
__anon5f703c9f0802null647 REQUIRE_THROWS_AS( [&] {
648 vector_type vec2(list.begin(), list.end());
649 utils::suppress_unused_warning(vec2);
650 }(), const std::bad_alloc);
651
652 allocator_type::init_counters();
653 allocator_type::set_limits(0);
654 vector_type src_vec(42, 42.);
655 allocator_type::set_limits(1);
656
__anon5f703c9f0902null657 REQUIRE_THROWS_AS( [&] {
658 vector_type vec3(src_vec, allocator_type{});
659 utils::suppress_unused_warning(vec3);
660 }(), const std::bad_alloc);
661 }
662 #endif // TBB_USE_EXCEPTIONS
663
664 //! \brief \ref regression \ref error_guessing
665 TEST_CASE("Reducing concurrent_vector") {
666 constexpr int final_sum = 100000;
667 tbb::concurrent_vector<int> vec(final_sum, 1);
668 const tbb::concurrent_vector<int> cvec(vec);
669
670 CHECK(reduce_vector(vec.range()) == final_sum);
671 CHECK(reduce_vector(cvec.range()) == final_sum);
672 }
673
674
675 //! \brief \ref error_guessing
676 TEST_CASE("swap with not always equal allocators"){
677 using allocator_type = NotAlwaysEqualAllocator<int>;
678 using vector_type = tbb::concurrent_vector<int, allocator_type>;
679
680 vector_type vec1{};
681 vector_type vec2(42, 42);
682
683 swap(vec1, vec2);
684
685 CHECK(vec2.empty());
686 }
687
688 // The problem was that after allocating first_block,
689 // no write was made to the embedded table.
690 // Also, two threads could be in the table extension section at once.
691 // NOTE: If the implementation of the vector has an issue, this test will either hang
692 // or fail with the assertion in debug mode.
693 //! \brief \ref regression
694 TEST_CASE("Testing vector in a highly concurrent environment") {
695 for (std::size_t i = 0; i < 10000; ++i) {
696 tbb::concurrent_vector<int> test_vec;
697
__anon5f703c9f0a02(const tbb::blocked_range<std::size_t>&) 698 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, 10000), [&] (const tbb::blocked_range<std::size_t>&) {
699 test_vec.grow_by(1);
700 }, tbb::static_partitioner{});
701
702 REQUIRE(test_vec.size() == utils::get_platform_max_threads());
703 }
704 }
705
706 #if __TBB_CPP20_CONCEPTS_PRESENT
707 //! \brief \ref error_guessing
708 TEST_CASE("container_range concept for concurrent_vector ranges") {
709 static_assert(test_concepts::container_range<typename tbb::concurrent_vector<int>::range_type>);
710 static_assert(test_concepts::container_range<typename tbb::concurrent_vector<int>::const_range_type>);
711 }
712 #endif // __TBB_CPP20_CONCEPTS_PRESENT
713
714 // There was a bug in concurrent_vector that was reproduced when resize marked
715 // segment (that owned by my_first_block) as deleted and
716 // on segment allocation thread is stuck waiting this segment to be published by other thread that allocated first block.
717 //! Testing resize behavior for case when new size lesser than old size.
718 //! \brief \ref regression
719 TEST_CASE("testing resize on sequantual mode") {
720 tbb::concurrent_vector<int> v;
721
722 v.resize(382);
723 CHECK(v.size() == 382);
724 while (v.size() < 737) {
725 v.emplace_back();
726 }
727 CHECK(v.size() == 737);
728
729 v.resize(27);
730 CHECK(v.size() == 27);
731 while (v.size() < 737) {
732 v.emplace_back();
733 }
734 CHECK(v.size() == 737);
735
736 v.resize(1);
737 CHECK(v.size() == 1);
738 while (v.size() < 40) {
739 v.emplace_back();
740 }
741 CHECK(v.size() == 40);
742
743 v.resize(2222);
744 CHECK(v.size() == 2222);
745 while (v.size() < 4444) {
746 v.emplace_back();
747 }
748 CHECK(v.size() == 4444);
749
750 v.clear();
751 CHECK(v.size() == 0);
752 }
753