]> gcc.gnu.org Git - gcc.git/commitdiff
libstdc++: Replace try-catch in std::list::merge to avoid O(N) size
authorJonathan Wakely <jwakely@redhat.com>
Wed, 29 Sep 2021 19:46:55 +0000 (20:46 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Thu, 7 Jul 2022 19:59:52 +0000 (20:59 +0100)
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.

(cherry picked from commit b8d42cfa84fb31e592411e6cea41bdde980c51d7)

libstdc++-v3/include/bits/list.tcc
libstdc++-v3/include/bits/stl_list.h

index 0ce4c47a90e25b2e04368ac9aa2f33de701739a3..62b0ba1063abf06d3248e71da14769ada2562b60 100644 (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;
              }
          }
       }
index dbe05a9834b29f4c010ba5bee72957d0d97fd276..2c70adf2b937cb6788dfa09ac8dd7f3466bbc0c8 100644 (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
@@ -1966,6 +1967,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
This page took 0.054725 seconds and 5 git commands to generate.