From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 17089 invoked by alias); 18 Aug 2017 20:04:19 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Received: (qmail 15737 invoked by uid 89); 18 Aug 2017 20:04:17 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-25.9 required=5.0 tests=BAYES_00,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,KAM_LAZY_DOMAIN_SECURITY,RP_MATCHES_RCVD,SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=algo, adapt X-Spam-User: qpsmtpd, 2 recipients X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Fri, 18 Aug 2017 20:04:15 +0000 Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 315C180F6D; Fri, 18 Aug 2017 20:04:14 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 315C180F6D Authentication-Results: ext-mx03.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx03.extmail.prod.ext.phx2.redhat.com; spf=fail smtp.mailfrom=jwakely@redhat.com Received: from localhost (unknown [10.33.36.3]) by smtp.corp.redhat.com (Postfix) with ESMTP id D76055C7A2; Fri, 18 Aug 2017 20:04:13 +0000 (UTC) Date: Fri, 18 Aug 2017 21:17:00 -0000 From: Jonathan Wakely To: =?iso-8859-1?Q?Fran=E7ois?= Dumont Cc: "libstdc++@gcc.gnu.org" , gcc-patches Subject: Re: std::list optimizations Message-ID: <20170818200412.GM4582@redhat.com> References: <11158ff8-dcec-d34e-a1e4-288719ff35ca@gmail.com> <6b5b0901-6673-54d4-0e86-cdbc7f84ac67@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1; format=flowed Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <6b5b0901-6673-54d4-0e86-cdbc7f84ac67@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett User-Agent: Mutt/1.8.3 (2017-05-23) X-SW-Source: 2017-08/txt/msg01152.txt.bz2 On 28/07/17 18:42 +0200, François Dumont wrote: >Hi > > Completing execution of the testsuite revealed a bug. So here is >the correct version of this patch. > >François > >On 21/07/2017 19:14, François Dumont wrote: >>Hi >> >> Here is a proposal for 2 optimizations in the std::list >>implementation. >> >> Optimization on the move constructor taking an allocator for >>always equal allocators. Compare to the version in my previous >>std::list patch I am now doing it at std::list level rather than at >>_List_base level. This way we won't instantiate the insert call and >>we won't check for empty list when the allocator always compare >>equal. >> >> 2nd optimization, I replace the _S_distance method by the >>std::distance algo which benefit from the nice [begin(), end()) >>range optimization when cxx11 abi is being used. >> >> Note that I am proposing the 2 change in 1 patch to save some >>review time but I can commit those separately. >> >>Tested under x86_64 Linux normal mode. >> >> >> * include/bits/stl_list.h >> (_List_base<>::_S_distance): Remove. >> (_List_impl(_List_impl&&, _Node_alloc_type&&)): New. >> (_List_base(_List_base&&, _Node_alloc_type&&)): Use latter. >> (_List_base(_Node_alloc_type&&)): New. >> (_List_base<>::_M_distance, _List_base<>::_M_node_count): Move... >> (list<>::_M_distance, list<>::_M_node_count): ...here. Replace >>calls to >> _S_distance with calls to std::distance. >> (list(list&&, const allocator_type&, true_type)): New. >> (list(list&&, const allocator_type&, false_type)): New. >> (list(list&&, const allocator_type&)): Adapt to call latters. >> >>Ok to commit ? >> >>François >> > >diff --git a/libstdc++-v3/include/bits/stl_list.h b/libstdc++-v3/include/bits/stl_list.h >index cef94f7..eb71a5e 100644 >--- a/libstdc++-v3/include/bits/stl_list.h >+++ b/libstdc++-v3/include/bits/stl_list.h >@@ -364,19 +364,6 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > rebind<_List_node<_Tp> >::other _Node_alloc_type; > typedef __gnu_cxx::__alloc_traits<_Node_alloc_type> _Node_alloc_traits; > >- static size_t >- _S_distance(const __detail::_List_node_base* __first, >- const __detail::_List_node_base* __last) >- { >- size_t __n = 0; >- while (__first != __last) >- { >- __first = __first->_M_next; >- ++__n; >- } >- return __n; >- } >- Removing this function will break code that expects it to exist in an explicit instantiation. The function could be left there, but unused. > struct _List_impl > : public _Node_alloc_type > { >@@ -393,6 +380,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > #if __cplusplus >= 201103L > _List_impl(_List_impl&&) = default; > >+ _List_impl(_List_impl&& __x, _Node_alloc_type&& __a) >+ : _Node_alloc_type(std::move(__a)), _M_node(std::move(__x._M_node)) >+ { } >+ This is fine. > _List_impl(_Node_alloc_type&& __a) noexcept > : _Node_alloc_type(std::move(__a)) > { } >@@ -409,28 +400,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > void _M_inc_size(size_t __n) { _M_impl._M_node._M_size += __n; } > > void _M_dec_size(size_t __n) { _M_impl._M_node._M_size -= __n; } >- >- size_t >- _M_distance(const __detail::_List_node_base* __first, >- const __detail::_List_node_base* __last) const >- { return _S_distance(__first, __last); } >- >- // return the stored size >- size_t _M_node_count() const { return _M_get_size(); } > #else > // dummy implementations used when the size is not stored > size_t _M_get_size() const { return 0; } > void _M_set_size(size_t) { } > void _M_inc_size(size_t) { } > void _M_dec_size(size_t) { } >- size_t _M_distance(const void*, const void*) const { return 0; } >- >- // count the number of nodes >- size_t _M_node_count() const >- { >- return _S_distance(_M_impl._M_node._M_next, >- std::__addressof(_M_impl._M_node)); >- } > #endif Same problem again: these will no longer be defined by explicit instantiations. > > typename _Node_alloc_traits::pointer >@@ -466,12 +441,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > _List_base(_List_base&&) = default; > > _List_base(_List_base&& __x, _Node_alloc_type&& __a) >+ : _M_impl(std::move(__x._M_impl), std::move(__a)) >+ { } >+ >+ _List_base(_Node_alloc_type&& __a) > : _M_impl(std::move(__a)) >- { >- if (__x._M_get_Node_allocator() == _M_get_Node_allocator()) >- _M_move_nodes(std::move(__x)); >- // else caller must move individual elements. >- } >+ { } > I like this change in principle, but it alters the behaviour of an existing constructor. Existing code might use the constructor and get broken by this. You can avoid that by leaving the existing constructor alone and adding two new ones for new code to use. Reordering the parameters will make the new one distinct from the old one: // Used when is_always_equal _List_base(_Node_alloc_type&& __a, _List_base&& __x)) : _M_impl(std::move(__x._M_impl), std::move(__a)) { } // Used when !is_always_equal _List_base(_Node_alloc_type&& __a) : _M_impl(std::move(__a)) { } Or: // Used when is_always_equal _List_base(_List_base&& __x, _Node_alloc_type&& __a, true_type) : _M_impl(std::move(__x._M_impl), std::move(__a)) { } // Used when !is_always_equal _List_base(_Node_alloc_type&& __a) : _M_impl(std::move(__a)) { } Either of these forms will add new constructors for the new semantics. > void > _M_move_nodes(_List_base&& __x) >@@ -616,6 +591,25 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > } > #endif > >+#if _GLIBCXX_USE_CXX11_ABI >+ size_t >+ _M_distance(const_iterator __first, const_iterator __last) const >+ { return std::distance(__first, __last); } >+ >+ // return the stored size >+ size_t >+ _M_node_count() const >+ { return this->_M_get_size(); } >+#else >+ // dummy implementations used when the size is not stored >+ size_t _M_distance(const_iterator, const_iterator) const { return 0; } >+ >+ // count the number of nodes >+ size_t >+ _M_node_count() const >+ { return std::distance(begin(), end()); } >+#endif Adding these is fine. New code will use them. Old code relying on explicit instantiations will still find the definitions of the old functions and use them. _M_distance could be static though, neither version uses the 'this' pointer, so it would be called _S_distance. >+ > public: > // [23.2.2.1] construct/copy/destroy > // (assign() and get_allocator() are also listed in this section) >@@ -718,15 +712,27 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > : _Base(_Node_alloc_type(__a)) > { _M_initialize_dispatch(__x.begin(), __x.end(), __false_type()); } > >- list(list&& __x, const allocator_type& __a) >- noexcept(_Node_alloc_traits::_S_always_equal()) >+ private: >+ list(list&& __x, const allocator_type& __a, true_type) noexcept > : _Base(std::move(__x), _Node_alloc_type(__a)) Either reorder the arguments, or add true_type{} to the end, to call the new constructor suggested above. >+ { } >+ >+ list(list&& __x, const allocator_type& __a, false_type) >+ : _Base(_Node_alloc_type(__a)) > { >- // If __x is not empty it means its allocator is not equal to __a, >- // so we need to move from each element individually. >- insert(begin(), std::__make_move_if_noexcept_iterator(__x.begin()), >- std::__make_move_if_noexcept_iterator(__x.end())); >+ if (__x._M_get_Node_allocator() == this->_M_get_Node_allocator()) >+ this->_M_move_nodes(std::move(__x)); >+ else >+ insert(begin(), std::__make_move_if_noexcept_iterator(__x.begin()), >+ std::__make_move_if_noexcept_iterator(__x.end())); > } >+ >+ public: >+ list(list&& __x, const allocator_type& __a) >+ noexcept(_Node_alloc_traits::_S_always_equal()) >+ : list(std::move(__x), __a, >+ typename _Node_alloc_traits::is_always_equal{}) >+ { } > #endif > > /** >@@ -1000,7 +1006,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > /** Returns the number of elements in the %list. */ > size_type > size() const _GLIBCXX_NOEXCEPT >- { return this->_M_node_count(); } >+ { return _M_node_count(); } > > /** Returns the size() of the largest possible %list. */ > size_type >@@ -1578,7 +1584,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > if (this != std::__addressof(__x)) > _M_check_equal_allocators(__x); > >- size_t __n = this->_M_distance(__first._M_node, __last._M_node); >+ size_t __n = _M_distance(__first, __last); > this->_M_inc_size(__n); > __x._M_dec_size(__n); > If _M_distance becomes static this would call _S_distance instead. Do those suggestions make sense? The idea is to ensure that a given function signature continues to have the same effects. To introduce new effects, use a new signature.