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: [PATCH][Hashtable 6/6] PR 68303 small size optimization
Date: Wed, 5 Jan 2022 17:07:44 +0000 [thread overview]
Message-ID: <CACb0b4=jObNkMRV0rLtOc97Fj4X1QEExr5fPicVsEOjmbGDaBg@mail.gmail.com> (raw)
In-Reply-To: <7b48215d-b56f-b726-c90d-87591a711c2a@gmail.com>
On Sat, 25 Dec 2021 at 21:39, François Dumont <frs.dumont@gmail.com> wrote:
> On 23/12/21 2:03 pm, Jonathan Wakely wrote:
> > On 21/12/21 07:07 +0100, François Dumont wrote:
> >> Hi
> >>
> >> ??? Is there a chance for this patch to be integrated for next gcc
> >> release ?
> >
> > Yes, I think this can still make it for GCC 12 (the patch was first
> > posted long ago in stage 1 and it's just me being slow to review it).
> >
> > But I have some questions and comments ...
> >
> >
> >> diff --git a/libstdc++-v3/include/bits/hashtable.h
> >> b/libstdc++-v3/include/bits/hashtable.h
> >> index 6e2d4c10cfe..6b2c6b99fef 100644
> >> --- a/libstdc++-v3/include/bits/hashtable.h
> >> +++ b/libstdc++-v3/include/bits/hashtable.h
> >> @@ -419,6 +419,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >> _M_uses_single_bucket() const
> >> { return _M_uses_single_bucket(_M_buckets); }
> >>
> >> + static constexpr size_t
> >> + __small_size_threshold()
> >> + {
> >> + return
> >> + __detail::_Hashtable_hash_traits<_Hash>::__small_size_threshold();
> >> + }
> >> +
> I've added the noexcept qualification as you asked.
> >> __hashtable_alloc&
> >> _M_base_alloc() { return *this; }
> >>
> >> @@ -788,6 +795,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >> _M_bucket_index(__hash_code __c) const
> >> { return __hash_code_base::_M_bucket_index(__c,
> >> _M_bucket_count); }
> >>
> >> + __node_base_ptr
> >> + _M_find_before_node(const key_type&);
> >> +
> >> // Find and insert helper functions and types
> >> // Find the node before the one matching the criteria.
> >> __node_base_ptr
> >> @@ -831,6 +841,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >> __node_base_ptr
> >> _M_get_previous_node(size_type __bkt, __node_ptr __n);
> >>
> >> + pair<const_iterator, __hash_code>
> >> + _M_compute_hash_code(const_iterator __hint, const key_type&
> >> __k) const;
> >> +
> >> // Insert node __n with hash code __code, in bucket __bkt if no
> >> // rehash (assumes no element with same key already present).
> >> // Takes ownership of __n if insertion succeeds, throws otherwise.
> >> @@ -1126,7 +1139,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >> void _M_rehash(size_type __bkt_count, const __rehash_state&
> >> __state);
> >> };
> >>
> >> -
> >> // Definitions of class template _Hashtable's out-of-line member
> >> functions.
> >> template<typename _Key, typename _Value, typename _Alloc,
> >> typename _ExtractKey, typename _Equal,
> >> @@ -1628,6 +1640,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >> find(const key_type& __k)
> >> -> iterator
> >> {
> >> + if (size() <= __small_size_threshold())
> >> + {
> >> + for (auto __it = begin(); __it != end(); ++__it)
> >> + if (this->_M_key_equals(__k, *__it._M_cur))
> >> + return __it;
> >> + return end();
> >> + }
> >
> > This loop is repeated a few times, would it be better factored out
> > into its own function? _M_find_loop or something? The return type is
> > different in some cases, so maybe it's OK like this.
> Yes, I also thought about that but there is often small changes from one
> loop to another as you noticed.
> >
> >
> >
> >> +
> >> __hash_code __code = this->_M_hash_code(__k);
> >> std::size_t __bkt = _M_bucket_index(__code);
> >> return iterator(_M_find_node(__bkt, __k, __code));
> >> @@ -1643,6 +1663,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >> find(const key_type& __k) const
> >> -> const_iterator
> >> {
> >> + if (size() <= __small_size_threshold())
> >> + {
> >> + for (auto __it = begin(); __it != end(); ++__it)
> >> + if (this->_M_key_equals(__k, *__it._M_cur))
> >> + return __it;
> >> + return end();
> >> + }
> >> +
> >> __hash_code __code = this->_M_hash_code(__k);
> >> std::size_t __bkt = _M_bucket_index(__code);
> >> return const_iterator(_M_find_node(__bkt, __k, __code));
> >> @@ -1855,6 +1883,35 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >> }
> >> #endif
> >>
> >> + // Find the node before the one whose key compares equal to k.
> >> + // Return nullptr if no node is found.
> >> + template<typename _Key, typename _Value, typename _Alloc,
> >> + typename _ExtractKey, typename _Equal,
> >> + typename _Hash, typename _RangeHash, typename _Unused,
> >> + typename _RehashPolicy, typename _Traits>
> >> + auto
> >> + _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
> >> + _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
> >> + _M_find_before_node(const key_type& __k)
> >> + -> __node_base_ptr
> >> + {
> >> + __node_base_ptr __prev_p = &_M_before_begin;
> >
> > This is OK now, but would need to be std::__addressof(_M_before_begin)
> > if/when the _Hash_code_base type becomes dependent on the allocator's
> > pointer.
> Yes, maybe after gcc release we will talk about those fancy pointer
> types again.
> >
> > __n_last = __n_last->_M_next();
> >> diff --git a/libstdc++-v3/include/bits/hashtable_policy.h
> >> b/libstdc++-v3/include/bits/hashtable_policy.h
> >> index 0b5443fc187..b4a8af081ce 100644
> >> --- a/libstdc++-v3/include/bits/hashtable_policy.h
> >> +++ b/libstdc++-v3/include/bits/hashtable_policy.h
> >> @@ -246,6 +246,20 @@ namespace __detail
> >> using __unique_keys = __bool_constant<_Unique_keys>;
> >> };
> >>
> >> + /**
> >> + * struct _Hashtable_hash_traits
> >> + *
> >> + * Important traits for hash tables depending on associated hasher.
> >> + *
> >> + */
> >> + template<typename _Hash>
> >> + struct _Hashtable_hash_traits
> >> + {
> >> + static constexpr std::size_t
> >> + __small_size_threshold()
> >> + { return std::__is_fast_hash<_Hash>::value ? 0 : 20; }
> >> + };
> >
> > Yet another trait that nobody is ever going to specialize make me sad.
> > I don't have a better idea though.
>
> Sure, but maybe I can document it ?
>
> I also wonder why you did not propose to make it a constant rather than
> requesting to add the noexcept.
>
> >
> > {
> >> @@ -1263,6 +1279,14 @@ namespace __detail
> >> const _Hash_node_value<_Value, __cache_hash_code>& __n) const
> >> { return _M_hash_code(_ExtractKey{}(__n._M_v())); }
> >>
> >> + __hash_code
> >> + _M_hash_code(const _Hash_node_value<_Value, false>& __n) const
> >> + { return _M_hash_code(_ExtractKey{}(__n._M_v())); }
> >> +
> >> + __hash_code
> >> + _M_hash_code(const _Hash_node_value<_Value, true>& __n) const
> >> + { return __n._M_hash_code; }
> >> +
> >> std::size_t
> >> _M_bucket_index(__hash_code __c, std::size_t __bkt_count) const
> >> { return _RangeHash{}(__c, __bkt_count); }
> >> @@ -1273,17 +1297,14 @@ namespace __detail
> >> noexcept( noexcept(declval<const _Hash&>()(declval<const _Key&>()))
> >> && noexcept(declval<const _RangeHash&>()((__hash_code)0,
> >> (std::size_t)0)) )
> >> - {
> >> - return _RangeHash{}(_M_hash_code(_ExtractKey{}(__n._M_v())),
> >> - __bkt_count);
> >> - }
> >> + { return _M_bucket_index(_M_hash_code(__n), __bkt_count); }
> >
> > Why add this extra level of indirection (and overload resolution)?
> >
> > We know this is a _Hash_node_value<V, false>, why call _M_hash_code to
> > decide how to get the hash code for it?
>
> Just to avoid code duplication but indeed it introduces overload
> resolution so I reverted it.
>
> >
> >
> >> diff --git a/libstdc++-v3/testsuite/util/testsuite_performance.h
> >> b/libstdc++-v3/testsuite/util/testsuite_performance.h
> >> index cba3a0d4b17..4ca15ab0e71 100644
> >> --- a/libstdc++-v3/testsuite/util/testsuite_performance.h
> >> +++ b/libstdc++-v3/testsuite/util/testsuite_performance.h
> >> @@ -239,7 +239,7 @@ namespace __gnu_test
> >> out << std::setw(4) << t.real_time() << "r" << space;
> >> out << std::setw(4) << t.user_time() << "u" << space;
> >> out << std::setw(4) << t.system_time() << "s" << space;
> >> - out << std::setw(8) << r.allocated_memory() << "mem" << space;
> >> + out << std::setw(9) << r.allocated_memory() << "mem" << space;
> >
> > One day I need to figure out why the reported memory is garbage so
> > often
>
>
> Ok with those changes ?
>
>
Yes, thanks very much (and bonne année).
prev parent reply other threads:[~2022-01-05 17:08 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-11-17 21:31 François Dumont
2020-07-17 12:58 ` Jonathan Wakely
2021-08-16 19:03 ` François Dumont
2021-09-23 4:36 ` François Dumont
2021-12-21 6:07 ` François Dumont
2021-12-21 6:28 ` Daniel Krügler
2021-12-21 17:55 ` François Dumont
2021-12-23 12:43 ` Jonathan Wakely
[not found] ` <YcRzoSSc534Lg+/F@redhat.com>
2021-12-25 21:39 ` François Dumont
2022-01-05 17:07 ` Jonathan Wakely [this message]
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='CACb0b4=jObNkMRV0rLtOc97Fj4X1QEExr5fPicVsEOjmbGDaBg@mail.gmail.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).