| /* |
| Copyright 2005-2010 Intel Corporation. All Rights Reserved. |
| |
| This file is part of Threading Building Blocks. |
| |
| Threading Building Blocks is free software; you can redistribute it |
| and/or modify it under the terms of the GNU General Public License |
| version 2 as published by the Free Software Foundation. |
| |
| Threading Building Blocks is distributed in the hope that it will be |
| useful, but WITHOUT ANY WARRANTY; without even the implied warranty |
| of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with Threading Building Blocks; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| |
| As a special exception, you may use this file as part of a free software |
| library without restriction. Specifically, if other files instantiate |
| templates or use macros or inline functions from this file, or you compile |
| this file and link it with other files to produce an executable, this |
| file does not by itself cause the resulting executable to be covered by |
| the GNU General Public License. This exception does not however |
| invalidate any other reasons why the executable file might be covered by |
| the GNU General Public License. |
| */ |
| |
| #include "tbb/concurrent_vector.h" |
| #include "tbb/cache_aligned_allocator.h" |
| #include "tbb/tbb_exception.h" |
| #include "tbb_misc.h" |
| #include "itt_notify.h" |
| |
| #if !TBB_USE_EXCEPTIONS && _MSC_VER |
| // Suppress "C++ exception handler used, but unwind semantics are not enabled" warning in STL headers |
| #pragma warning (push) |
| #pragma warning (disable: 4530) |
| #endif |
| |
| #include <cstring> |
| |
| #if !TBB_USE_EXCEPTIONS && _MSC_VER |
| #pragma warning (pop) |
| #endif |
| |
| #if defined(_MSC_VER) && defined(_Wp64) |
| // Workaround for overzealous compiler warnings in /Wp64 mode |
| #pragma warning (disable: 4267) |
| #endif |
| |
| using namespace std; |
| |
| namespace tbb { |
| |
| namespace internal { |
| class concurrent_vector_base_v3::helper :no_assign { |
| public: |
| //! memory page size |
| static const size_type page_size = 4096; |
| |
| inline static bool incompact_predicate(size_type size) { // assert size != 0, see source/test/test_vector_layout.cpp |
| return size < page_size || ((size-1)%page_size < page_size/2 && size < page_size * 128); // for more details |
| } |
| |
| inline static size_type find_segment_end(const concurrent_vector_base_v3 &v) { |
| segment_t *s = v.my_segment; |
| segment_index_t u = s==v.my_storage? pointers_per_short_table : pointers_per_long_table; |
| segment_index_t k = 0; |
| while( k < u && s[k].array > internal::vector_allocation_error_flag ) |
| ++k; |
| return k; |
| } |
| |
| // TODO: optimize accesses to my_first_block |
| //! assign first segment size. k - is index of last segment to be allocated, not a count of segments |
| inline static void assign_first_segment_if_neccessary(concurrent_vector_base_v3 &v, segment_index_t k) { |
| if( !v.my_first_block ) { |
| /* There was a suggestion to set first segment according to incompact_predicate: |
| while( k && !helper::incompact_predicate(segment_size( k ) * element_size) ) |
| --k; // while previous vector size is compact, decrement |
| // reasons to not do it: |
| // * constructor(n) is not ready to accept fragmented segments |
| // * backward compatibility due to that constructor |
| // * current version gives additional guarantee and faster init. |
| // * two calls to reserve() will give the same effect. |
| */ |
| v.my_first_block.compare_and_swap(k+1, 0); // store number of segments |
| } |
| } |
| |
| inline static void *allocate_segment(concurrent_vector_base_v3 &v, size_type n) { |
| void *ptr = v.vector_allocator_ptr(v, n); |
| if(!ptr) throw_exception(eid_bad_alloc); // check for bad allocation, throw exception |
| return ptr; |
| } |
| |
| //! Publish segment so other threads can see it. |
| inline static void publish_segment( segment_t& s, void* rhs ) { |
| // see also itt_store_pointer_with_release_v3() |
| ITT_NOTIFY( sync_releasing, &s.array ); |
| __TBB_store_with_release( s.array, rhs ); |
| } |
| |
| static size_type enable_segment(concurrent_vector_base_v3 &v, size_type k, size_type element_size); |
| |
| // TODO: rename as get_segments_table() and return segment pointer |
| inline static void extend_table_if_necessary(concurrent_vector_base_v3 &v, size_type k, size_type start ) { |
| if(k >= pointers_per_short_table && v.my_segment == v.my_storage) |
| extend_segment_table(v, start ); |
| } |
| |
| static void extend_segment_table(concurrent_vector_base_v3 &v, size_type start); |
| |
| inline static segment_t &acquire_segment(concurrent_vector_base_v3 &v, size_type index, size_type element_size, bool owner) { |
| segment_t &s = v.my_segment[index]; // TODO: pass v.my_segment as arument |
| if( !__TBB_load_with_acquire(s.array) ) { // do not check for internal::vector_allocation_error_flag |
| if( owner ) { |
| enable_segment( v, index, element_size ); |
| } else { |
| ITT_NOTIFY(sync_prepare, &s.array); |
| spin_wait_while_eq( s.array, (void*)0 ); |
| ITT_NOTIFY(sync_acquired, &s.array); |
| } |
| } else { |
| ITT_NOTIFY(sync_acquired, &s.array); |
| } |
| if( s.array <= internal::vector_allocation_error_flag ) // check for internal::vector_allocation_error_flag |
| throw_exception(eid_bad_last_alloc); // throw custom exception, because it's hard to recover after internal::vector_allocation_error_flag correctly |
| return s; |
| } |
| |
| ///// non-static fields of helper for exception-safe iteration across segments |
| segment_t *table;// TODO: review all segment_index_t as just short type |
| size_type first_block, k, sz, start, finish, element_size; |
| helper(segment_t *segments, size_type fb, size_type esize, size_type index, size_type s, size_type f) noexcept |
| : table(segments), first_block(fb), k(index), sz(0), start(s), finish(f), element_size(esize) {} |
| inline void first_segment() noexcept { |
| __TBB_ASSERT( start <= finish, NULL ); |
| __TBB_ASSERT( first_block || !finish, NULL ); |
| if( k < first_block ) k = 0; // process solid segment at a time |
| size_type base = segment_base( k ); |
| __TBB_ASSERT( base <= start, NULL ); |
| finish -= base; start -= base; // rebase as offsets from segment k |
| sz = k ? base : segment_size( first_block ); // sz==base for k>0 |
| } |
| inline void next_segment() noexcept { |
| finish -= sz; start = 0; // offsets from next segment |
| if( !k ) k = first_block; |
| else { ++k; sz <<= 1; } |
| } |
| template<typename F> |
| inline size_type apply(const F &func) { |
| first_segment(); |
| while( sz < finish ) { // work for more than one segment |
| func( table[k], static_cast<char*>(table[k].array)+element_size*start, sz-start ); |
| next_segment(); |
| } |
| func( table[k], static_cast<char*>(table[k].array)+element_size*start, finish-start ); |
| return k; |
| } |
| inline void *get_segment_ptr(size_type index, bool wait) { |
| segment_t &s = table[index]; |
| if( !__TBB_load_with_acquire(s.array) && wait ) { |
| ITT_NOTIFY(sync_prepare, &s.array); |
| spin_wait_while_eq( s.array, (void*)0 ); |
| ITT_NOTIFY(sync_acquired, &s.array); |
| } |
| return s.array; |
| } |
| ~helper() { |
| if( sz >= finish ) return; // the work is done correctly |
| cleanup(); |
| } |
| |
| //! Out of line code to assists destructor in infrequent cases. |
| void cleanup(); |
| |
| /// TODO: turn into lambda functions when available |
| struct init_body { |
| internal_array_op2 func; |
| const void *arg; |
| init_body(internal_array_op2 init, const void *src) : func(init), arg(src) {} |
| void operator()(segment_t &, void *begin, size_type n) const { |
| func( begin, arg, n ); |
| } |
| }; |
| struct safe_init_body { |
| internal_array_op2 func; |
| const void *arg; |
| safe_init_body(internal_array_op2 init, const void *src) : func(init), arg(src) {} |
| void operator()(segment_t &s, void *begin, size_type n) const { |
| if( s.array <= internal::vector_allocation_error_flag ) |
| throw_exception(eid_bad_last_alloc); // throw custom exception |
| func( begin, arg, n ); |
| } |
| }; |
| struct destroy_body { |
| internal_array_op1 func; |
| destroy_body(internal_array_op1 destroy) : func(destroy) {} |
| void operator()(segment_t &s, void *begin, size_type n) const { |
| if( s.array > internal::vector_allocation_error_flag ) |
| func( begin, n ); |
| } |
| }; |
| }; |
| |
| void concurrent_vector_base_v3::helper::extend_segment_table(concurrent_vector_base_v3 &v, concurrent_vector_base_v3::size_type start) { |
| if( start > segment_size(pointers_per_short_table) ) start = segment_size(pointers_per_short_table); |
| // If other threads are trying to set pointers in the short segment, wait for them to finish their |
| // assigments before we copy the short segment to the long segment. Note: grow_to_at_least depends on it |
| for( segment_index_t i = 0; segment_base(i) < start && v.my_segment == v.my_storage; i++ ) |
| if(!v.my_storage[i].array) { |
| ITT_NOTIFY(sync_prepare, &v.my_storage[i].array); |
| atomic_backoff backoff; |
| do backoff.pause(); while( v.my_segment == v.my_storage && !v.my_storage[i].array ); |
| ITT_NOTIFY(sync_acquired, &v.my_storage[i].array); |
| } |
| if( v.my_segment != v.my_storage ) return; |
| |
| segment_t* s = (segment_t*)NFS_Allocate( pointers_per_long_table, sizeof(segment_t), NULL ); |
| // No need to check !s here, because NFS_Allocate throws exception if it cannot allocate the requested storage. |
| memset( s, 0, pointers_per_long_table*sizeof(segment_t) ); |
| for( segment_index_t i = 0; i < pointers_per_short_table; i++) |
| s[i] = v.my_storage[i]; |
| if( v.my_segment.compare_and_swap( s, v.my_storage ) != v.my_storage ) |
| NFS_Free( s ); |
| // else TODO: add ITT_NOTIFY signals for v.my_segment? |
| } |
| |
| concurrent_vector_base_v3::size_type concurrent_vector_base_v3::helper::enable_segment(concurrent_vector_base_v3 &v, concurrent_vector_base_v3::size_type k, concurrent_vector_base_v3::size_type element_size) { |
| segment_t* s = v.my_segment; // TODO: optimize out as argument? Optimize accesses to my_first_block |
| __TBB_ASSERT( s[k].array <= internal::vector_allocation_error_flag, "concurrent operation during growth?" ); |
| if( !k ) { |
| assign_first_segment_if_neccessary(v, default_initial_segments-1); |
| __TBB_TRY { |
| publish_segment(s[0], allocate_segment(v, segment_size(v.my_first_block) ) ); |
| } __TBB_CATCH(...) { // intercept exception here, assign internal::vector_allocation_error_flag value, re-throw exception |
| publish_segment(s[0], internal::vector_allocation_error_flag); |
| __TBB_RETHROW(); |
| } |
| return 2; |
| } |
| size_type m = segment_size(k); |
| if( !v.my_first_block ) // push_back only |
| spin_wait_while_eq( v.my_first_block, segment_index_t(0) ); |
| if( k < v.my_first_block ) { |
| // s[0].array is changed only once ( 0 -> !0 ) and points to uninitialized memory |
| void *array0 = __TBB_load_with_acquire(s[0].array); |
| if( !array0 ) { |
| // sync_prepare called only if there is a wait |
| ITT_NOTIFY(sync_prepare, &s[0].array ); |
| spin_wait_while_eq( s[0].array, (void*)0 ); |
| array0 = __TBB_load_with_acquire(s[0].array); |
| } |
| ITT_NOTIFY(sync_acquired, &s[0].array); |
| if( array0 <= internal::vector_allocation_error_flag ) { // check for internal::vector_allocation_error_flag of initial segment |
| publish_segment(s[k], internal::vector_allocation_error_flag); // and assign internal::vector_allocation_error_flag here |
| throw_exception(eid_bad_last_alloc); // throw custom exception |
| } |
| publish_segment( s[k], |
| static_cast<void*>( static_cast<char*>(array0) + segment_base(k)*element_size ) |
| ); |
| } else { |
| __TBB_TRY { |
| publish_segment(s[k], allocate_segment(v, m)); |
| } __TBB_CATCH(...) { // intercept exception here, assign internal::vector_allocation_error_flag value, re-throw exception |
| publish_segment(s[k], internal::vector_allocation_error_flag); |
| __TBB_RETHROW(); |
| } |
| } |
| return m; |
| } |
| |
| void concurrent_vector_base_v3::helper::cleanup() { |
| if( !sz ) { // allocation failed, restore the table |
| segment_index_t k_start = k, k_end = segment_index_of(finish-1); |
| if( segment_base( k_start ) < start ) |
| get_segment_ptr(k_start++, true); // wait |
| if( k_start < first_block ) { |
| void *array0 = get_segment_ptr(0, start>0); // wait if necessary |
| if( array0 && !k_start ) ++k_start; |
| if( array0 <= internal::vector_allocation_error_flag ) |
| for(; k_start < first_block && k_start <= k_end; ++k_start ) |
| publish_segment(table[k_start], internal::vector_allocation_error_flag); |
| else for(; k_start < first_block && k_start <= k_end; ++k_start ) |
| publish_segment(table[k_start], static_cast<void*>( |
| static_cast<char*>(array0) + segment_base(k_start)*element_size) ); |
| } |
| for(; k_start <= k_end; ++k_start ) // not in first block |
| if( !__TBB_load_with_acquire(table[k_start].array) ) |
| publish_segment(table[k_start], internal::vector_allocation_error_flag); |
| // fill alocated items |
| first_segment(); |
| goto recover; |
| } |
| while( sz <= finish ) { // there is still work for at least one segment |
| next_segment(); |
| recover: |
| void *array = table[k].array; |
| if( array > internal::vector_allocation_error_flag ) |
| std::memset( static_cast<char*>(array)+element_size*start, 0, ((sz<finish?sz:finish) - start)*element_size ); |
| else __TBB_ASSERT( array == internal::vector_allocation_error_flag, NULL ); |
| } |
| } |
| |
| concurrent_vector_base_v3::~concurrent_vector_base_v3() { |
| segment_t* s = my_segment; |
| if( s != my_storage ) { |
| // Clear short segment. |
| for( segment_index_t i = 0; i < pointers_per_short_table; i++) |
| my_storage[i].array = NULL; |
| #if TBB_USE_DEBUG |
| for( segment_index_t i = 0; i < pointers_per_long_table; i++) |
| __TBB_ASSERT( my_segment[i].array <= internal::vector_allocation_error_flag, "Segment should have been freed. Please recompile with new TBB before using exceptions."); |
| #endif |
| my_segment = my_storage; |
| NFS_Free( s ); |
| } |
| } |
| |
| concurrent_vector_base_v3::size_type concurrent_vector_base_v3::internal_capacity() const { |
| return segment_base( helper::find_segment_end(*this) ); |
| } |
| |
| void concurrent_vector_base_v3::internal_throw_exception(size_type t) const { |
| switch(t) { |
| case 0: throw_exception(eid_out_of_range); |
| case 1: throw_exception(eid_segment_range_error); |
| case 2: throw_exception(eid_index_range_error); |
| } |
| } |
| |
| void concurrent_vector_base_v3::internal_reserve( size_type n, size_type element_size, size_type max_size ) { |
| if( n>max_size ) |
| throw_exception(eid_reservation_length_error); |
| __TBB_ASSERT( n, NULL ); |
| helper::assign_first_segment_if_neccessary(*this, segment_index_of(n-1)); |
| segment_index_t k = helper::find_segment_end(*this); |
| __TBB_TRY { |
| for( ; segment_base(k)<n; ++k ) { |
| helper::extend_table_if_necessary(*this, k, 0); |
| if(my_segment[k].array <= internal::vector_allocation_error_flag) |
| helper::enable_segment(*this, k, element_size); |
| } |
| } __TBB_CATCH(...) { |
| my_segment[k].array = NULL; |
| __TBB_RETHROW(); // repair and rethrow |
| } |
| } |
| |
| void concurrent_vector_base_v3::internal_copy( const concurrent_vector_base_v3& src, size_type element_size, internal_array_op2 copy ) { |
| size_type n = src.my_early_size; |
| __TBB_ASSERT( my_segment == my_storage, NULL); |
| if( n ) { |
| helper::assign_first_segment_if_neccessary(*this, segment_index_of(n-1)); |
| size_type b; |
| for( segment_index_t k=0; (b=segment_base(k))<n; ++k ) { |
| if( (src.my_segment == (segment_t*)src.my_storage && k >= pointers_per_short_table) |
| || src.my_segment[k].array <= internal::vector_allocation_error_flag ) { |
| my_early_size = b; break; |
| } |
| helper::extend_table_if_necessary(*this, k, 0); |
| size_type m = helper::enable_segment(*this, k, element_size); |
| if( m > n-b ) m = n-b; |
| my_early_size = b+m; |
| copy( my_segment[k].array, src.my_segment[k].array, m ); |
| } |
| } |
| } |
| |
| void concurrent_vector_base_v3::internal_assign( const concurrent_vector_base_v3& src, size_type element_size, internal_array_op1 destroy, internal_array_op2 assign, internal_array_op2 copy ) { |
| size_type n = src.my_early_size; |
| while( my_early_size>n ) { // TODO: improve |
| segment_index_t k = segment_index_of( my_early_size-1 ); |
| size_type b=segment_base(k); |
| size_type new_end = b>=n ? b : n; |
| __TBB_ASSERT( my_early_size>new_end, NULL ); |
| if( my_segment[k].array <= internal::vector_allocation_error_flag) // check vector was broken before |
| throw_exception(eid_bad_last_alloc); // throw custom exception |
| // destructors are supposed to not throw any exceptions |
| destroy( (char*)my_segment[k].array+element_size*(new_end-b), my_early_size-new_end ); |
| my_early_size = new_end; |
| } |
| size_type dst_initialized_size = my_early_size; |
| my_early_size = n; |
| helper::assign_first_segment_if_neccessary(*this, segment_index_of(n)); |
| size_type b; |
| for( segment_index_t k=0; (b=segment_base(k))<n; ++k ) { |
| if( (src.my_segment == (segment_t*)src.my_storage && k >= pointers_per_short_table) |
| || src.my_segment[k].array <= internal::vector_allocation_error_flag ) { // if source is damaged |
| my_early_size = b; break; // TODO: it may cause undestructed items |
| } |
| helper::extend_table_if_necessary(*this, k, 0); |
| if( !my_segment[k].array ) |
| helper::enable_segment(*this, k, element_size); |
| else if( my_segment[k].array <= internal::vector_allocation_error_flag ) |
| throw_exception(eid_bad_last_alloc); // throw custom exception |
| size_type m = k? segment_size(k) : 2; |
| if( m > n-b ) m = n-b; |
| size_type a = 0; |
| if( dst_initialized_size>b ) { |
| a = dst_initialized_size-b; |
| if( a>m ) a = m; |
| assign( my_segment[k].array, src.my_segment[k].array, a ); |
| m -= a; |
| a *= element_size; |
| } |
| if( m>0 ) |
| copy( (char*)my_segment[k].array+a, (char*)src.my_segment[k].array+a, m ); |
| } |
| __TBB_ASSERT( src.my_early_size==n, "detected use of concurrent_vector::operator= with right side that was concurrently modified" ); |
| } |
| |
| void* concurrent_vector_base_v3::internal_push_back( size_type element_size, size_type& index ) { |
| __TBB_ASSERT( sizeof(my_early_size)==sizeof(uintptr_t), NULL ); |
| size_type tmp = __TBB_FetchAndIncrementWacquire(&my_early_size); |
| index = tmp; |
| segment_index_t k_old = segment_index_of( tmp ); |
| size_type base = segment_base(k_old); |
| helper::extend_table_if_necessary(*this, k_old, tmp); |
| segment_t& s = helper::acquire_segment(*this, k_old, element_size, base==tmp); |
| size_type j_begin = tmp-base; |
| return (void*)((char*)s.array+element_size*j_begin); |
| } |
| |
| void concurrent_vector_base_v3::internal_grow_to_at_least( size_type new_size, size_type element_size, internal_array_op2 init, const void *src ) { |
| internal_grow_to_at_least_with_result( new_size, element_size, init, src ); |
| } |
| |
| concurrent_vector_base_v3::size_type concurrent_vector_base_v3::internal_grow_to_at_least_with_result( size_type new_size, size_type element_size, internal_array_op2 init, const void *src ) { |
| size_type e = my_early_size; |
| while( e<new_size ) { |
| size_type f = my_early_size.compare_and_swap(new_size,e); |
| if( f==e ) { |
| internal_grow( e, new_size, element_size, init, src ); |
| break; |
| } |
| e = f; |
| } |
| // Check/wait for segments allocation completes |
| segment_index_t i, k_old = segment_index_of( new_size-1 ); |
| if( k_old >= pointers_per_short_table && my_segment == my_storage ) { |
| spin_wait_while_eq( my_segment, my_storage ); |
| } |
| for( i = 0; i <= k_old; ++i ) { |
| segment_t &s = my_segment[i]; |
| if(!s.array) { |
| ITT_NOTIFY(sync_prepare, &s.array); |
| atomic_backoff backoff; |
| do backoff.pause(); |
| while( !__TBB_load_with_acquire(my_segment[i].array) ); // my_segment may change concurrently |
| ITT_NOTIFY(sync_acquired, &s.array); |
| } |
| if( my_segment[i].array <= internal::vector_allocation_error_flag ) |
| throw_exception(eid_bad_last_alloc); |
| } |
| #if TBB_USE_DEBUG |
| size_type capacity = internal_capacity(); |
| __TBB_ASSERT( capacity >= new_size, NULL); |
| #endif |
| return e; |
| } |
| |
| concurrent_vector_base_v3::size_type concurrent_vector_base_v3::internal_grow_by( size_type delta, size_type element_size, internal_array_op2 init, const void *src ) { |
| size_type result = my_early_size.fetch_and_add(delta); |
| internal_grow( result, result+delta, element_size, init, src ); |
| return result; |
| } |
| |
| void concurrent_vector_base_v3::internal_grow( const size_type start, size_type finish, size_type element_size, internal_array_op2 init, const void *src ) { |
| __TBB_ASSERT( start<finish, "start must be less than finish" ); |
| segment_index_t k_start = segment_index_of(start), k_end = segment_index_of(finish-1); |
| helper::assign_first_segment_if_neccessary(*this, k_end); |
| helper::extend_table_if_necessary(*this, k_end, start); |
| helper range(my_segment, my_first_block, element_size, k_start, start, finish); |
| for(; k_end > k_start && k_end >= range.first_block; --k_end ) // allocate segments in reverse order |
| helper::acquire_segment(*this, k_end, element_size, true/*for k_end>k_start*/); |
| for(; k_start <= k_end; ++k_start ) // but allocate first block in straight order |
| helper::acquire_segment(*this, k_start, element_size, segment_base( k_start ) >= start ); |
| range.apply( helper::init_body(init, src) ); |
| } |
| |
| void concurrent_vector_base_v3::internal_resize( size_type n, size_type element_size, size_type max_size, const void *src, |
| internal_array_op1 destroy, internal_array_op2 init ) { |
| size_type j = my_early_size; |
| if( n > j ) { // construct items |
| internal_reserve(n, element_size, max_size); |
| my_early_size = n; |
| helper for_each(my_segment, my_first_block, element_size, segment_index_of(j), j, n); |
| for_each.apply( helper::safe_init_body(init, src) ); |
| } else { |
| my_early_size = n; |
| helper for_each(my_segment, my_first_block, element_size, segment_index_of(n), n, j); |
| for_each.apply( helper::destroy_body(destroy) ); |
| } |
| } |
| |
| concurrent_vector_base_v3::segment_index_t concurrent_vector_base_v3::internal_clear( internal_array_op1 destroy ) { |
| __TBB_ASSERT( my_segment, NULL ); |
| size_type j = my_early_size; |
| my_early_size = 0; |
| helper for_each(my_segment, my_first_block, 0, 0, 0, j); // element_size is safe to be zero if 'start' is zero |
| j = for_each.apply( helper::destroy_body(destroy) ); |
| size_type i = helper::find_segment_end(*this); |
| return j < i? i : j+1; |
| } |
| |
| void *concurrent_vector_base_v3::internal_compact( size_type element_size, void *table, internal_array_op1 destroy, internal_array_op2 copy ) |
| { |
| const size_type my_size = my_early_size; |
| const segment_index_t k_end = helper::find_segment_end(*this); // allocated segments |
| const segment_index_t k_stop = my_size? segment_index_of(my_size-1) + 1 : 0; // number of segments to store existing items: 0=>0; 1,2=>1; 3,4=>2; [5-8]=>3;.. |
| const segment_index_t first_block = my_first_block; // number of merged segments, getting values from atomics |
| |
| segment_index_t k = first_block; |
| if(k_stop < first_block) |
| k = k_stop; |
| else |
| while (k < k_stop && helper::incompact_predicate(segment_size( k ) * element_size) ) k++; |
| if(k_stop == k_end && k == first_block) |
| return NULL; |
| |
| segment_t *const segment_table = my_segment; |
| internal_segments_table &old = *static_cast<internal_segments_table*>( table ); |
| memset(&old, 0, sizeof(old)); |
| |
| if ( k != first_block && k ) // first segment optimization |
| { |
| // exception can occur here |
| void *seg = old.table[0] = helper::allocate_segment( *this, segment_size(k) ); |
| old.first_block = k; // fill info for freeing new segment if exception occurs |
| // copy items to the new segment |
| size_type my_segment_size = segment_size( first_block ); |
| for (segment_index_t i = 0, j = 0; i < k && j < my_size; j = my_segment_size) { |
| __TBB_ASSERT( segment_table[i].array > internal::vector_allocation_error_flag, NULL); |
| void *s = static_cast<void*>( |
| static_cast<char*>(seg) + segment_base(i)*element_size ); |
| if(j + my_segment_size >= my_size) my_segment_size = my_size - j; |
| __TBB_TRY { // exception can occur here |
| copy( s, segment_table[i].array, my_segment_size ); |
| } __TBB_CATCH(...) { // destroy all the already copied items |
| helper for_each(reinterpret_cast<segment_t*>(&old.table[0]), old.first_block, element_size, |
| 0, 0, segment_base(i)+my_segment_size); |
| for_each.apply( helper::destroy_body(destroy) ); |
| __TBB_RETHROW(); |
| } |
| my_segment_size = i? segment_size( ++i ) : segment_size( i = first_block ); |
| } |
| // commit the changes |
| memcpy(old.table, segment_table, k * sizeof(segment_t)); |
| for (segment_index_t i = 0; i < k; i++) { |
| segment_table[i].array = static_cast<void*>( |
| static_cast<char*>(seg) + segment_base(i)*element_size ); |
| } |
| old.first_block = first_block; my_first_block = k; // now, first_block != my_first_block |
| // destroy original copies |
| my_segment_size = segment_size( first_block ); // old.first_block actually |
| for (segment_index_t i = 0, j = 0; i < k && j < my_size; j = my_segment_size) { |
| if(j + my_segment_size >= my_size) my_segment_size = my_size - j; |
| // destructors are supposed to not throw any exceptions |
| destroy( old.table[i], my_segment_size ); |
| my_segment_size = i? segment_size( ++i ) : segment_size( i = first_block ); |
| } |
| } |
| // free unnecessary segments allocated by reserve() call |
| if ( k_stop < k_end ) { |
| old.first_block = first_block; |
| memcpy(old.table+k_stop, segment_table+k_stop, (k_end-k_stop) * sizeof(segment_t)); |
| memset(segment_table+k_stop, 0, (k_end-k_stop) * sizeof(segment_t)); |
| if( !k ) my_first_block = 0; |
| } |
| return table; |
| } |
| |
| void concurrent_vector_base_v3::internal_swap(concurrent_vector_base_v3& v) |
| { |
| size_type my_sz = my_early_size, v_sz = v.my_early_size; |
| if(!my_sz && !v_sz) return; |
| size_type tmp = my_first_block; my_first_block = v.my_first_block; v.my_first_block = tmp; |
| bool my_short = (my_segment == my_storage), v_short = (v.my_segment == v.my_storage); |
| if ( my_short && v_short ) { // swap both tables |
| char tbl[pointers_per_short_table * sizeof(segment_t)]; |
| memcpy(tbl, my_storage, pointers_per_short_table * sizeof(segment_t)); |
| memcpy(my_storage, v.my_storage, pointers_per_short_table * sizeof(segment_t)); |
| memcpy(v.my_storage, tbl, pointers_per_short_table * sizeof(segment_t)); |
| } |
| else if ( my_short ) { // my -> v |
| memcpy(v.my_storage, my_storage, pointers_per_short_table * sizeof(segment_t)); |
| my_segment = v.my_segment; v.my_segment = v.my_storage; |
| } |
| else if ( v_short ) { // v -> my |
| memcpy(my_storage, v.my_storage, pointers_per_short_table * sizeof(segment_t)); |
| v.my_segment = my_segment; my_segment = my_storage; |
| } else { |
| segment_t *ptr = my_segment; my_segment = v.my_segment; v.my_segment = ptr; |
| } |
| my_early_size = v_sz; v.my_early_size = my_sz; |
| } |
| |
| } // namespace internal |
| |
| } // tbb |