public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
From: Jonathan Wakely <jwakely@redhat.com>
To: "François Dumont" <frs.dumont@gmail.com>
Cc: "libstdc++@gcc.gnu.org" <libstdc++@gcc.gnu.org>,
	gcc-patches <gcc-patches@gcc.gnu.org>
Subject: Re: Rb_tree constructor optimization
Date: Wed, 02 May 2018 11:49:00 -0000	[thread overview]
Message-ID: <20180502114913.GV20930@redhat.com> (raw)
In-Reply-To: <f40ddfa2-8870-00cd-ef51-48aca7af7aff@gmail.com>

On 01/05/18 21:56 +0200, François Dumont wrote:
>Hi
>
>If not told otherwise I'll commit attached patch tomorrow.

Please do not commit it, see below.

>Already discussed here:
>
>https://gcc.gnu.org/ml/libstdc++/2017-10/msg00053.html

There's no changelog entry with the patch, so to recap, the changes
are:

- noexcept specifications are automatically deduced instead of being
  stated explicitly.
 
- The allocator-extended move constructors get correct exception
  specifications in Debug Mode (consistent with normal mode). This
  should be done anyway, independent of any other changes.

- We avoid a `if (__x._M_root() != nullptr)` branch in the case where
  the allocator-extended move constructor is used with an always-equal
  allocator, right?


Deducing the noexcept specifications probably has a (small?) impact on
compilation speed, as currently the allocator-extended move
constructors have:

-      noexcept(is_nothrow_copy_constructible<_Compare>::value
-	       && _Alloc_traits::_S_always_equal())

After the patch they have to determine the noexcept-ness of the
_Rep_type constructor taking allocator_type, which has to determine
the noexcept-ness of the _Rep_type constructor taking a
_Node_allocator, which has to determine the noexcept-ness of the
constructor it dispatches to depending on _S_always_equal(), which is
simply:

+      noexcept(is_nothrow_move_constructible<_Rb_tree_impl<_Compare>>::value)

So instead of just using that value in the first place we have to
perform three rounds of overload resolution to find the right
constructor and then check its exception specification. And then we
get the wrong answer (see below).

Compilation time for containers is already **much** slower since the
allocator-extended constructors were added for GCC 5.x, despite very
few people ever using those constructors. This patch seems to require
even more work from the compiler to parse constructors nobody uses.


>François
>

>diff --git a/libstdc++-v3/include/bits/stl_map.h b/libstdc++-v3/include/bits/stl_map.h
>index a4a026e..2b8fd27 100644
>--- a/libstdc++-v3/include/bits/stl_map.h
>+++ b/libstdc++-v3/include/bits/stl_map.h
>@@ -240,8 +240,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 
>       /// Allocator-extended move constructor.
>       map(map&& __m, const allocator_type& __a)
>-      noexcept(is_nothrow_copy_constructible<_Compare>::value
>-	       && _Alloc_traits::_S_always_equal())
>+      noexcept( noexcept(
>+	_Rep_type(std::move(__m._M_t), declval<_Pair_alloc_type>())) )

All these calls to declval need to be qualified to avoid ADL.

It seems strange to have a mix of real values and declval expressions,
rather than:

	_Rep_type(std::declval<_Rep_type>(), std::declval<_Pair_alloc_type>())) )



>       : _M_t(std::move(__m._M_t), _Pair_alloc_type(__a)) { }
> 
>       /// Allocator-extended initialier-list constructor.
>diff --git a/libstdc++-v3/include/bits/stl_multimap.h b/libstdc++-v3/include/bits/stl_multimap.h
>index fc8f454..b9289ab 100644
>--- a/libstdc++-v3/include/bits/stl_multimap.h
>+++ b/libstdc++-v3/include/bits/stl_multimap.h
>@@ -237,8 +237,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 
>       /// Allocator-extended move constructor.
>       multimap(multimap&& __m, const allocator_type& __a)
>-      noexcept(is_nothrow_copy_constructible<_Compare>::value
>-	       && _Alloc_traits::_S_always_equal())
>+      noexcept( noexcept(
>+	_Rep_type(std::move(__m._M_t), declval<_Pair_alloc_type>())) )
>       : _M_t(std::move(__m._M_t), _Pair_alloc_type(__a)) { }
> 
>       /// Allocator-extended initialier-list constructor.
>diff --git a/libstdc++-v3/include/bits/stl_multiset.h b/libstdc++-v3/include/bits/stl_multiset.h
>index f41f56c..afaf3b6 100644
>--- a/libstdc++-v3/include/bits/stl_multiset.h
>+++ b/libstdc++-v3/include/bits/stl_multiset.h
>@@ -253,8 +253,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 
>       /// Allocator-extended move constructor.
>       multiset(multiset&& __m, const allocator_type& __a)
>-      noexcept(is_nothrow_copy_constructible<_Compare>::value
>-	       && _Alloc_traits::_S_always_equal())
>+      noexcept( noexcept(
>+	_Rep_type(std::move(__m._M_t), declval<_Key_alloc_type>())) )
>       : _M_t(std::move(__m._M_t), _Key_alloc_type(__a)) { }
> 
>       /// Allocator-extended initialier-list constructor.
>diff --git a/libstdc++-v3/include/bits/stl_set.h b/libstdc++-v3/include/bits/stl_set.h
>index 2e332ef..fc6d1f7 100644
>--- a/libstdc++-v3/include/bits/stl_set.h
>+++ b/libstdc++-v3/include/bits/stl_set.h
>@@ -257,8 +257,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 
>       /// Allocator-extended move constructor.
>       set(set&& __x, const allocator_type& __a)
>-      noexcept(is_nothrow_copy_constructible<_Compare>::value
>-	       && _Alloc_traits::_S_always_equal())
>+      noexcept( noexcept(
>+	_Rep_type(std::move(__x._M_t), declval<_Key_alloc_type>())) )
>       : _M_t(std::move(__x._M_t), _Key_alloc_type(__a)) { }
> 
>       /// Allocator-extended initialier-list constructor.
>diff --git a/libstdc++-v3/include/bits/stl_tree.h b/libstdc++-v3/include/bits/stl_tree.h
>index d0a8448..732ac55 100644
>--- a/libstdc++-v3/include/bits/stl_tree.h
>+++ b/libstdc++-v3/include/bits/stl_tree.h
>@@ -715,6 +715,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> #else
> 	  _Rb_tree_impl(_Rb_tree_impl&&) = default;
> 
>+	  _Rb_tree_impl(_Rb_tree_impl&& __x, _Node_allocator&& __a)
>+	  : _Node_allocator(std::move(__a)),
>+	    _Base_key_compare(std::move(__x)),
>+	    _Rb_tree_header(std::move(__x))
>+	  { }
>+
> 	  _Rb_tree_impl(const _Key_compare& __comp, _Node_allocator&& __a)
> 	  : _Node_allocator(std::move(__a)), _Base_key_compare(__comp)
> 	  { }
>@@ -955,10 +961,32 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       _Rb_tree(_Rb_tree&&) = default;
> 
>       _Rb_tree(_Rb_tree&& __x, const allocator_type& __a)
>+      noexcept( noexcept(
>+	_Rb_tree(std::move(__x), declval<_Node_allocator>())) )
>       : _Rb_tree(std::move(__x), _Node_allocator(__a))
>       { }
> 
>-      _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a);
>+    private:
>+      _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a, true_type)
>+      noexcept(is_nothrow_move_constructible<_Rb_tree_impl<_Compare>>::value)

This is wrong.

The result of is_nothrow_move_constructible<_Rb_tree_impl<_Compare>>
depends on this constructor:

  _Rb_tree_impl(_Rb_tree_impl&&) = default;

That constructor depends on the allocator type, which might not be
noexcept, but it's undefined behaviour for it to throw. We don't want
the exception-specification to depend on the allocator, otherwise we
fail this test on the line marked XXX:

template<typename T>
struct Alloc : std::allocator<T>
{
  Alloc() { }
  Alloc(const Alloc&) { }
  template<typename U> Alloc(const Alloc<U>&) { }

  template<typename U> struct rebind { using other = Alloc<U>; };
};

template<typename A>
using Set = std::set<int, std::less<int>, A>;

template<typename A>
  using Test = std::is_nothrow_constructible<Set<A>, Set<A>, A>;

int main()
{
  static_assert(Test<std::allocator<int>>::value, "");
  static_assert(Test<Alloc<int>>::value, "");  // XXX
}


The changes to deduce the exception-specifications seem to make the
code more fragile -- do we really want to make those changes?


>+      : _M_impl(std::move(__x._M_impl), std::move(__a))
>+      { }
>+
>+      _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a, false_type)
>+      : _M_impl(__x._M_impl._M_key_compare, std::move(__a))
>+      {
>+	if (__x._M_root() != nullptr)
>+	  _M_move_data(__x, false_type{});
>+      }
>+
>+    public:
>+      _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a)
>+      noexcept( noexcept(
>+	_Rb_tree(std::move(__x), std::move(__a),
>+		 declval<typename _Alloc_traits::is_always_equal>()) ) )
>+      : _Rb_tree(std::move(__x), std::move(__a),
>+		 typename _Alloc_traits::is_always_equal{})
>+      { }
> #endif


>diff --git a/libstdc++-v3/include/debug/map.h b/libstdc++-v3/include/debug/map.h
>index 414b4dc..9edb7dc 100644
>--- a/libstdc++-v3/include/debug/map.h
>+++ b/libstdc++-v3/include/debug/map.h
>@@ -105,8 +105,10 @@ namespace __debug
>       : _Base(__m, __a) { }
> 
>       map(map&& __m, const allocator_type& __a)
>+      noexcept( noexcept(_Base(std::move(__m._M_base()), __a)) )
>       : _Safe(std::move(__m._M_safe()), __a),
>-	_Base(std::move(__m._M_base()), __a) { }
>+	_Base(std::move(__m._M_base()), __a)
>+      { }

This part of the patch is actually a bug fix, making the debug mode
consistent with normal mode.

>diff --git a/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_move_construct.cc b/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_move_construct.cc
>index 0041408..8791eb8 100644
>--- a/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_move_construct.cc
>+++ b/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_move_construct.cc
>@@ -23,4 +23,25 @@
> 
> typedef std::map<int, int> mtype;
> 
>-static_assert(std::is_nothrow_move_constructible<mtype>::value, "Error");
>+static_assert( std::is_nothrow_move_constructible<mtype>::value,
>+	       "noexcept move constructor" );
>+static_assert( noexcept( mtype(std::declval<mtype>(),
>+		std::declval<const typename mtype::allocator_type&>()) ),

This can use std::is_nothrow_constructible

>+	       "noexcept move constructor with allocator" );
>+
>+struct ExceptLess

Please name this "ThrowingLess" instead of "ExceptLess", I think
that's more descriptive.

>+{
>+  ExceptLess() = default;
>+  ExceptLess(const ExceptLess&) /* noexcept */
>+  { }
>+
>+  bool
>+  operator()(int l, int r) const
>+  { return l < r; }
>+};
>+
>+typedef std::map<int, int, ExceptLess> emtype;
>+
>+static_assert( !noexcept( emtype(std::declval<emtype>(),
>+		std::declval<const typename emtype::allocator_type&>()) ),

This can use std::is_nothrow_constructible too.

Same comments for the other tests.


  parent reply	other threads:[~2018-05-02 11:49 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-05-01 19:56 François Dumont
2018-05-01 21:23 ` Ville Voutilainen
2018-05-01 21:28   ` Jakub Jelinek
2018-05-01 21:47     ` Ville Voutilainen
2018-05-01 22:42       ` Jonathan Wakely
2018-05-02 11:49 ` Jonathan Wakely [this message]
2018-05-03 20:11   ` François Dumont
2018-05-06 14:06   ` François Dumont
2018-05-11 12:16     ` Jonathan Wakely
2018-05-15  5:31       ` François Dumont
2018-05-17 15:10         ` Jonathan Wakely
2018-05-19  1:45           ` Jonathan Wakely
2018-05-19 23:16             ` H.J. Lu
2018-05-21 19:59               ` François Dumont
  -- strict thread matches above, loose matches on Subject: below --
2017-08-28 19:13 François Dumont
2017-09-08 15:50 ` Jonathan Wakely
2017-09-13 19:57   ` François Dumont
2017-09-14 20:04     ` François Dumont
2017-10-19  5:39       ` François Dumont

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180502114913.GV20930@redhat.com \
    --to=jwakely@redhat.com \
    --cc=frs.dumont@gmail.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=libstdc++@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).