libstdc++: Replace try-catch in std::list::merge to avoid O(N) size

The current std::list::merge code calls size() before starting to merge
any elements, so that the _M_size members can be updated after the merge
finishes. The work is done in a try-block so that the sizes can still be
updated in an exception handler if any element comparison throws.

The _M_size members only exist for the cxx11 ABI, so the initial call to
size() and the try-catch are only needed for that ABI. For the old ABI
the size() call performs an O(N) list traversal to get a value that
isn't even used, and catching exceptions just to rethrow them isn't
needed either.

This refactors the merge functions to remove the try-catch block and use
an RAII type instead. For the cxx11 ABI that type's destructor updates
the list sizes, and for the old ABI it's a no-op.

libstdc++-v3/ChangeLog:

	* include/bits/list.tcc (list::merge): Remove call to size() and
	try-catch block. Use _Finalize_merge instead.
	* include/bits/stl_list.h (list::_Finalize_merge): New
	scope guard type to update _M_size members after a merge.
This commit is contained in:
Jonathan Wakely 2021-09-29 20:46:55 +01:00
parent 5051fad858
commit b8d42cfa84
2 changed files with 63 additions and 43 deletions

View file

@ -416,29 +416,22 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
iterator __last1 = end();
iterator __first2 = __x.begin();
iterator __last2 = __x.end();
const size_t __orig_size = __x.size();
__try {
while (__first1 != __last1 && __first2 != __last2)
if (*__first2 < *__first1)
{
iterator __next = __first2;
_M_transfer(__first1, __first2, ++__next);
__first2 = __next;
}
else
++__first1;
if (__first2 != __last2)
_M_transfer(__last1, __first2, __last2);
this->_M_inc_size(__x._M_get_size());
__x._M_set_size(0);
}
__catch(...)
const _Finalize_merge __fin(*this, __x, __first2);
while (__first1 != __last1 && __first2 != __last2)
if (*__first2 < *__first1)
{
iterator __next = __first2;
_M_transfer(__first1, __first2, ++__next);
__first2 = __next;
}
else
++__first1;
if (__first2 != __last2)
{
const size_t __dist = std::distance(__first2, __last2);
this->_M_inc_size(__orig_size - __dist);
__x._M_set_size(__dist);
__throw_exception_again;
_M_transfer(__last1, __first2, __last2);
__first2 = __last2;
}
}
}
@ -463,30 +456,22 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
iterator __last1 = end();
iterator __first2 = __x.begin();
iterator __last2 = __x.end();
const size_t __orig_size = __x.size();
__try
{
while (__first1 != __last1 && __first2 != __last2)
if (__comp(*__first2, *__first1))
{
iterator __next = __first2;
_M_transfer(__first1, __first2, ++__next);
__first2 = __next;
}
else
++__first1;
if (__first2 != __last2)
_M_transfer(__last1, __first2, __last2);
this->_M_inc_size(__x._M_get_size());
__x._M_set_size(0);
}
__catch(...)
const _Finalize_merge __fin(*this, __x, __first2);
while (__first1 != __last1 && __first2 != __last2)
if (__comp(*__first2, *__first1))
{
iterator __next = __first2;
_M_transfer(__first1, __first2, ++__next);
__first2 = __next;
}
else
++__first1;
if (__first2 != __last2)
{
const size_t __dist = std::distance(__first2, __last2);
this->_M_inc_size(__orig_size - __dist);
__x._M_set_size(__dist);
__throw_exception_again;
_M_transfer(__last1, __first2, __last2);
__first2 = __last2;
}
}
}

View file

@ -1,6 +1,7 @@
// List implementation -*- C++ -*-
// Copyright (C) 2001-2021 Free Software Foundation, Inc.
// Copyright The GNU Toolchain Authors.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
@ -1992,6 +1993,40 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
__false_type{});
}
#endif
#if _GLIBCXX_USE_CXX11_ABI
// Update _M_size members after merging (some of) __src into __dest.
struct _Finalize_merge
{
explicit
_Finalize_merge(list& __dest, list& __src, const iterator& __src_next)
: _M_dest(__dest), _M_src(__src), _M_next(__src_next)
{ }
~_Finalize_merge()
{
// For the common case, _M_next == _M_sec.end() and the std::distance
// call is fast. But if any *iter1 < *iter2 comparison throws then we
// have to count how many elements remain in _M_src.
const size_t __num_unmerged = std::distance(_M_next, _M_src.end());
const size_t __orig_size = _M_src._M_get_size();
_M_dest._M_inc_size(__orig_size - __num_unmerged);
_M_src._M_set_size(__num_unmerged);
}
list& _M_dest;
list& _M_src;
const iterator& _M_next;
#if __cplusplus >= 201103L
_Finalize_merge(const _Finalize_merge&) = delete;
#endif
};
#else
struct _Finalize_merge
{ explicit _Finalize_merge(list&, list&, const iterator&) { } };
#endif
};
#if __cpp_deduction_guides >= 201606