libstdc++: Optimize std::vector construction from input iterators [PR108487]
LWG 3291 make std::ranges::iota_view's iterator have input_iterator_tag as its iterator_category, even though it satisfies the C++20 std::forward_iterator concept. This means that the traditional std::vector::vector(InputIterator, InputIterator) constructor treats iota_view iterators as input iterators, because it only understands the C++17 iterator requirements, not the C++20 iterator concepts. This results in a loop that calls emplace_back for each individual element of the iota_view, requiring the vector to reallocate repeatedly as the values are inserted. This makes it unnecessarily slow to construct a vector from an iota_view. This change adds a new _M_range_initialize_n function for initializing a vector from a range (which doesn't have to be common) and a size. This new function can be used by vector(InputIterator, InputIterator) and vector(from_range_t, R&&) when std::ranges::distance can be used to get the size. It can also be used by the _M_range_initialize overload that gets the size for a Cpp17ForwardIterator pair using std::distance, and by the vector(initializer_list) constructor. With this new function constructing a std::vector from iota_view does a single allocation of the correct size and so doesn't need to reallocate in a loop. Previously the _M_range_initialize overload for Cpp17ForwardIterator was using a local RAII _Guard_alloc object to own the storage, but that was redundant. The _Vector_base can own the storage right away, and its destructor will deallocate it if _M_range_initialize exits via an exception. libstdc++-v3/ChangeLog: PR libstdc++/108487 * include/bits/stl_vector.h (vector(initializer_list)): Call _M_range_initialize_n instead of _M_range_initialize. (vector(InputIterator, InputIterator)): Use _M_range_initialize_n for C++20 sized sentinels and forward iterators. (vector(from_range_t, R&&)): Use _M_range_initialize_n for sized ranges and forward ranges. (vector::_M_range_initialize(FwIt, FwIt, forward_iterator_tag)): Likewise. (vector::_M_range_initialize_n): New function. * testsuite/23_containers/vector/cons/108487.cc: New test. Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
This commit is contained in:
parent
2848b8dabc
commit
e200f53a55
2 changed files with 56 additions and 16 deletions
|
@ -65,6 +65,9 @@
|
|||
#if __cplusplus >= 202002L
|
||||
# include <compare>
|
||||
#endif
|
||||
#if __glibcxx_concepts // C++ >= C++20
|
||||
# include <bits/ranges_base.h> // ranges::distance
|
||||
#endif
|
||||
#if __glibcxx_ranges_to_container // C++ >= 23
|
||||
# include <bits/ranges_algobase.h> // ranges::copy
|
||||
# include <bits/ranges_util.h> // ranges::subrange
|
||||
|
@ -706,8 +709,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
|
|||
const allocator_type& __a = allocator_type())
|
||||
: _Base(__a)
|
||||
{
|
||||
_M_range_initialize(__l.begin(), __l.end(),
|
||||
random_access_iterator_tag());
|
||||
_M_range_initialize_n(__l.begin(), __l.end(), __l.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -735,6 +737,17 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
|
|||
const allocator_type& __a = allocator_type())
|
||||
: _Base(__a)
|
||||
{
|
||||
#if __glibcxx_concepts // C++ >= C++20
|
||||
if constexpr (sized_sentinel_for<_InputIterator, _InputIterator>
|
||||
|| forward_iterator<_InputIterator>)
|
||||
{
|
||||
const auto __n
|
||||
= static_cast<size_type>(ranges::distance(__first, __last));
|
||||
_M_range_initialize_n(__first, __last, __n);
|
||||
return;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
_M_range_initialize(__first, __last,
|
||||
std::__iterator_category(__first));
|
||||
}
|
||||
|
@ -763,13 +776,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
|
|||
{
|
||||
if constexpr (ranges::forward_range<_Rg> || ranges::sized_range<_Rg>)
|
||||
{
|
||||
const auto __n = size_type(ranges::distance(__rg));
|
||||
pointer __start =
|
||||
this->_M_allocate(_S_check_init_len(__n,
|
||||
_M_get_Tp_allocator()));
|
||||
this->_M_impl._M_finish = this->_M_impl._M_start = __start;
|
||||
this->_M_impl._M_end_of_storage = __start + __n;
|
||||
_Base::_M_append_range(__rg);
|
||||
const auto __n = static_cast<size_type>(ranges::distance(__rg));
|
||||
_M_range_initialize_n(ranges::begin(__rg), ranges::end(__rg),
|
||||
__n);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1962,15 +1971,22 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
|
|||
_M_range_initialize(_ForwardIterator __first, _ForwardIterator __last,
|
||||
std::forward_iterator_tag)
|
||||
{
|
||||
const size_type __n = std::distance(__first, __last);
|
||||
pointer __start =
|
||||
_M_range_initialize_n(__first, __last,
|
||||
std::distance(__first, __last));
|
||||
}
|
||||
|
||||
template<typename _Iterator, typename _Sentinel>
|
||||
_GLIBCXX20_CONSTEXPR
|
||||
void
|
||||
_M_range_initialize_n(_Iterator __first, _Sentinel __last,
|
||||
size_type __n)
|
||||
{
|
||||
pointer __start = this->_M_impl._M_start =
|
||||
this->_M_allocate(_S_check_init_len(__n, _M_get_Tp_allocator()));
|
||||
_Guard_alloc __guard(__start, __n, *this);
|
||||
this->_M_impl._M_finish = std::__uninitialized_copy_a
|
||||
(__first, __last, __start, _M_get_Tp_allocator());
|
||||
this->_M_impl._M_start = __start;
|
||||
(void) __guard._M_release();
|
||||
this->_M_impl._M_end_of_storage = __start + __n;
|
||||
this->_M_impl._M_finish
|
||||
= std::__uninitialized_copy_a(_GLIBCXX_MOVE(__first), __last,
|
||||
__start, _M_get_Tp_allocator());
|
||||
}
|
||||
|
||||
// Called by the first initialize_dispatch above and by the
|
||||
|
|
24
libstdc++-v3/testsuite/23_containers/vector/cons/108487.cc
Normal file
24
libstdc++-v3/testsuite/23_containers/vector/cons/108487.cc
Normal file
|
@ -0,0 +1,24 @@
|
|||
// { dg-do run { target c++20 } }
|
||||
// Bug libstdc++/108487
|
||||
// ~20-30x slowdown in populating std::vector from std::ranges::iota_view
|
||||
|
||||
#include <ranges>
|
||||
#include <testsuite_hooks.h>
|
||||
#include <testsuite_allocator.h>
|
||||
|
||||
void
|
||||
test_pr108487()
|
||||
{
|
||||
using __gnu_test::tracker_allocator;
|
||||
using __gnu_test::tracker_allocator_counter;
|
||||
auto r = std::ranges::iota_view{0, 20};
|
||||
tracker_allocator_counter::reset();
|
||||
std::vector<int, tracker_allocator<int>> v{r.begin(), r.end()};
|
||||
const std::size_t bytes = v.capacity() * sizeof(v.front());
|
||||
VERIFY( tracker_allocator_counter::get_allocation_count() == bytes );
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
test_pr108487();
|
||||
}
|
Loading…
Add table
Reference in a new issue