From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:03:54
Hi:
This series introduces multiple rehashing.
Recall that the original implementation in br_multicast used
two list pointers per hash node and therefore is limited to at
most one rehash at a time since you need one list pointer for
the old table and one for the new table.
Thanks to Josh Triplett's suggestion of using a single list pointer
we're no longer limited by that. So it is perfectly OK to have
an arbitrary number of tables in existence at any one time.
The reader and removal simply has to walk from the oldest table
to the newest table in order not to miss anything. Insertion
without lookup are just as easy as we simply go to the last table
that we can find and add the entry there.
However, insertion with uniqueness lookup is more complicated
because we need to ensure that two simultaneous insertions of the
same key do not both succeed. To achieve this, all insertions
including those without lookups are required to obtain the bucket
lock from the oldest hash table that is still alive. This is
determined by having the rehasher (there is only one rehashing
thread in the system) keep a pointer of where it is up to. If
a bucket has already been rehashed then it is dead, i.e., there
cannot be any more insertions to it, otherwise it is considered
alive. This guarantees that the same key cannot be inserted
in two different tables in parallel.
Patch 1 is actually a bug fix for the walker.
Patch 2-6 eliminates unnecessary out-of-line copies of jhash.
Patch 7 disables automatic shrinking so now shrinking is only
possible if requested by the user.
Patch 8 introduces multiple rehashing. This means that if we
decide to grow then we will grow regardless of whether the previous
one has finished. However, this is still asynchronous meaning
that if insertions come fast enough we may still end up with a
table that is overutilised.
Patch 9 adds support for GFP_ATOMIC allocations of struct bucket_table.
Finally patch 10 enables immediate rehashing. This is done either
when the table reaches 100% utilisation, or when the chain length
exceeds 16 (the latter can be disabled on request, e.g., for
nft_hash.
With these patches the system should no longer have any trouble
dealing with fast insertions on a small table. In the worst
case you end up with a list of tables that's log N in length
while the rehasher catches up.
v2 fixes the blank subject of patch 5 and the prevents vzalloc
for GFP_ATOMIC callers in patch 9.
Cheers,
--
Email: Herbert Xu [off-list ref]
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:20
The walker is a lockless reader so it too needs an smp_rmb before
reading the future_tbl field in order to see any new tables that
may contain elements that we should have walked over.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
lib/rhashtable.c | 3 +++
1 file changed, 3 insertions(+)
@@ -477,6 +477,9 @@ next:iter->skip=0;}+/* Ensure we see any new tables. */+smp_rmb();+iter->walker->tbl=rht_dereference_rcu(tbl->future_tbl,ht);if(iter->walker->tbl){iter->slot=0;
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:22
Since every current rhashtable user uses jhash as their hash
function, the fact that jhash is an inline function causes each
user to generate a copy of its code.
This function provides a solution to this problem by allowing
hashfn to be unset. In which case rhashtable will automatically
set it to jhash. Furthermore, if the key length is a multiple
of 4, we will switch over to jhash2.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
include/linux/rhashtable.h | 33 +++++++++++++++++++++++++++------
lib/rhashtable.c | 17 ++++++++++++++++-
2 files changed, 43 insertions(+), 7 deletions(-)
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:22
When rht_key_hashfn is called from rhashtable itself and params
is equal to ht->p, there is no point in checking params.key_len
and falling back to ht->p.key_len.
For some reason gcc couldn't figure out that params is the same
as ht->p. So let's help it by only checking params.key_len when
it's a constant.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
include/linux/rhashtable.h | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:23
This patch removes the explicit jhash value for the hashfn parameter
of rhashtable. The default is now jhash so removing the setting
makes no difference apart from making one less copy of jhash in
the kernel.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
net/netlink/af_netlink.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:24
This patch removes the explicit jhash value for the hashfn parameter
of rhashtable. The default is now jhash so removing the setting
makes no difference apart from making one less copy of jhash in
the kernel.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
net/tipc/socket.c | 2 --
1 file changed, 2 deletions(-)
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:27
This patch removes the explicit jhash value for the hashfn parameter
of rhashtable. The default is now jhash so removing the setting
makes no difference apart from making one less copy of jhash in
the kernel.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
net/netfilter/nft_hash.c | 2 --
1 file changed, 2 deletions(-)
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:27
Automatic shrinking is dangerous because it provides an easy
way for an adversary to cause us to do unnecessary work. Thus
making the resizable hashtable a poor data structure.
This patch disables automatic shrinking but retains a manual
shrink function for those cases where insertions and removals
are overseen by a trusted entity, e.g., nft_hash.
The shrink function will now also shrink to fit rather than halve
the size of the table.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
include/linux/rhashtable.h | 15 ---------------
lib/rhashtable.c | 44 ++++++++++++++++++++++++++++++--------------
lib/test_rhashtable.c | 16 ++++++----------
3 files changed, 36 insertions(+), 39 deletions(-)
@@ -252,19 +252,6 @@ static inline bool rht_grow_above_75(const struct rhashtable *ht,(!ht->p.max_size||tbl->size<ht->p.max_size);}-/**-*rht_shrink_below_30-returnstrueifnelems<0.3*table-size-*@ht:hashtable-*@tbl:currenttable-*/-staticinlineboolrht_shrink_below_30(conststructrhashtable*ht,-conststructbucket_table*tbl)-{-/* Shrink table beneath 30% load */-returnatomic_read(&ht->nelems)<(tbl->size*3/10)&&-tbl->size>ht->p.min_size;-}-/* The bucket lock is selected based on the hash and protects mutations*onagroupofhashbuckets.*
@@ -745,8 +732,6 @@ static inline int rhashtable_remove_fast(gotoout;atomic_dec(&ht->nelems);-if(rht_shrink_below_30(ht,tbl))-schedule_work(&ht->run_work);out:rcu_read_unlock();
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:29
This patch adds the ability to allocate bucket table with GFP_ATOMIC
instead of GFP_KERNEL. This is needed when we perform an immediate
rehash during insertion.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
lib/rhashtable.c | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:30
This patch adds the missing bits to allow multiple rehashes. The
read-side as well as remove already handle this correctly. So it's
only the rehasher and insertion that need modification to handle
this.
Note that this patch doesn't actually enable it so for now rehashing
is still only performed by the worker thread and a user thread if
an explicit shrinking is ordered.
This patch also disables the rhashtable_expand interface because
it is useless since the table is meant to expand automatically.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
include/linux/rhashtable.h | 24 ++++++----
lib/rhashtable.c | 99 ++++++++++++++++++++++++++++++---------------
lib/test_rhashtable.c | 23 +---------
3 files changed, 85 insertions(+), 61 deletions(-)
@@ -527,17 +526,22 @@ static inline int __rhashtable_insert_fast(rcu_read_lock();tbl=rht_dereference_rcu(ht->tbl,ht);-hash=rht_head_hashfn(ht,tbl,obj,params);-lock=rht_bucket_lock(tbl,hash);--spin_lock_bh(lock);-/* Because we have already taken the bucket lock in tbl,-*ifwefindthatfuture_tblisnotyetvisiblethen-*thatguaranteesallotherinsertionsofthesameentry-*willalsograbthebucketlockintblbecauseuntil-*therehashcompletesht->tblwon'tbechanged.+/* All insertions must grab the oldest table containing+*thehashedbucketthatisyettoberehashed.*/+for(;;){+hash=rht_head_hashfn(ht,tbl,obj,params);+lock=rht_bucket_lock(tbl,hash);+spin_lock_bh(lock);++if(tbl->rehash<=hash)+break;++spin_unlock_bh(lock);+tbl=rht_dereference_rcu(tbl->future_tbl,ht);+}+new_tbl=rht_dereference_rcu(tbl->future_tbl,ht);if(unlikely(new_tbl)){err=rhashtable_insert_slow(ht,key,obj,new_tbl);
@@ -196,12 +209,18 @@ static void rhashtable_rehash_chain(struct rhashtable *ht, unsigned old_hash)spin_unlock_bh(old_bucket_lock);}-staticvoidrhashtable_rehash(structrhashtable*ht,-structbucket_table*new_tbl)+staticintrhashtable_rehash_attach(structrhashtable*ht,+structbucket_table*old_tbl,+structbucket_table*new_tbl){-structbucket_table*old_tbl=rht_dereference(ht->tbl,ht);-structrhashtable_walker*walker;-unsignedold_hash;+/* Protect future_tbl using the first bucket lock. */+spin_lock_bh(old_tbl->locks);++/* Did somebody beat us to it? */+if(rcu_access_pointer(old_tbl->future_tbl)){+spin_unlock_bh(old_tbl->locks);+return-EEXIST;+}/* Make insertions go into the new, empty table right away. Deletions*andlookupswillbeattemptedinbothtablesuntilwesynchronize.
@@ -211,6 +230,22 @@ static void rhashtable_rehash(struct rhashtable *ht,/* Ensure the new table is visible to readers. */smp_wmb();+spin_unlock_bh(old_tbl->locks);++return0;+}++staticintrhashtable_rehash_table(structrhashtable*ht)+{+structbucket_table*old_tbl=rht_dereference(ht->tbl,ht);+structbucket_table*new_tbl;+structrhashtable_walker*walker;+unsignedold_hash;++new_tbl=rht_dereference(old_tbl->future_tbl,ht);+if(!new_tbl)+return0;+for(old_hash=0;old_hash<old_tbl->size;old_hash++)rhashtable_rehash_chain(ht,old_hash);
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-22 08:04:31
This patch reintroduces immediate rehash during insertion. If
we find during insertion that the table is full or the chain
length exceeds a set limit (currently 16 but may be disabled
with insecure_elasticity) then we will force an immediate rehash.
The rehash will contain an expansion if the table utilisation
exceeds 75%.
If this rehash fails then the insertion will fail. Otherwise the
insertion will be reattempted in the new hash table.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
include/linux/rhashtable.h | 32 +++++++++++++++-----
lib/rhashtable.c | 71 ++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 95 insertions(+), 8 deletions(-)
@@ -252,6 +256,17 @@ static inline bool rht_grow_above_75(const struct rhashtable *ht,(!ht->p.max_size||tbl->size<ht->p.max_size);}+/**+*rht_grow_above_100-returnstrueifnelems>table-size+*@ht:hashtable+*@tbl:currenttable+*/+staticinlineboolrht_grow_above_100(conststructrhashtable*ht,+conststructbucket_table*tbl)+{+returnatomic_read(&ht->nelems)>tbl->size;+}+/* The bucket lock is selected based on the hash and protects mutations*onagroupofhashbuckets.*
@@ -517,11 +532,12 @@ static inline int __rhashtable_insert_fast(.ht=ht,.key=key,};-interr=-EEXIST;structbucket_table*tbl,*new_tbl;structrhash_head*head;spinlock_t*lock;+unsignedelasticity;unsignedhash;+interr;rcu_read_lock();
@@ -543,22 +559,24 @@ static inline int __rhashtable_insert_fast(}new_tbl=rht_dereference_rcu(tbl->future_tbl,ht);-if(unlikely(new_tbl)){+if(unlikely(new_tbl||rht_grow_above_100(ht,tbl))){+slow_path:err=rhashtable_insert_slow(ht,key,obj,new_tbl);gotoout;}-if(!key)-gotoskip_lookup;-+err=-EEXIST;+elasticity=ht->elasticity;rht_for_each(head,tbl,hash){-if(unlikely(!(params.obj_cmpfn?+if(key&&+unlikely(!(params.obj_cmpfn?params.obj_cmpfn(&arg,rht_obj(ht,head)):rhashtable_compare(&arg,rht_obj(ht,head)))))gotoout;+if(!--elasticity)+gotoslow_path;}-skip_lookup:err=0;head=rht_dereference_bucket(tbl->buckets[hash],tbl,hash);
@@ -364,21 +364,80 @@ unlock:schedule_work(&ht->run_work);}+staticboolrhashtable_check_elasticity(structrhashtable*ht,+structbucket_table*tbl,+unsignedhash)+{+unsignedelasticity=ht->elasticity;+structrhash_head*head;++rht_for_each(head,tbl,hash)+if(!--elasticity)+returntrue;++returnfalse;+}++intrhashtable_expand_or_rehash(structrhashtable*ht,+structbucket_table*tbl)+{+structbucket_table*old_tbl;+structbucket_table*new_tbl;+unsignedintsize;+interr;++old_tbl=rht_dereference_rcu(ht->tbl,ht);+if(!tbl)+tbl=rhashtable_last_table(ht,old_tbl);++size=tbl->size;++if(rht_grow_above_75(ht,tbl))+size*=2;+/* More than two rehashes (not resizes) detected. */+elseif(WARN_ON(old_tbl!=tbl&&old_tbl->size==size))+return-EBUSY;++new_tbl=bucket_table_alloc(ht,size,GFP_ATOMIC);+if(new_tbl==NULL)+return-ENOMEM;++err=rhashtable_rehash_attach(ht,tbl,new_tbl);+if(err){+bucket_table_free(new_tbl);+if(err==-EEXIST)+err=0;+}else+schedule_work(&ht->run_work);++returnerr;+}+intrhashtable_insert_slow(structrhashtable*ht,constvoid*key,structrhash_head*obj,structbucket_table*tbl){structrhash_head*head;unsignedhash;-interr=-EEXIST;+interr;++if(!tbl)+gotorehash;+restart:tbl=rhashtable_last_table(ht,tbl);hash=head_hashfn(ht,tbl,obj);spin_lock_nested(rht_bucket_lock(tbl,hash),SINGLE_DEPTH_NESTING);+err=-EEXIST;if(key&&rhashtable_lookup_fast(ht,key,ht->p))gotoexit;+err=-EAGAIN;+if(rhashtable_check_elasticity(ht,tbl,hash)||+rht_grow_above_100(ht,tbl))+gotoexit;+err=0;head=rht_dereference_bucket(tbl->buckets[hash],tbl,hash);
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-22 10:47:45
On 03/22/15 at 07:03pm, Herbert Xu wrote:
The walker is a lockless reader so it too needs an smp_rmb before
reading the future_tbl field in order to see any new tables that
may contain elements that we should have walked over.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-22 11:07:57
On 03/22/15 at 07:04pm, Herbert Xu wrote:
When rht_key_hashfn is called from rhashtable itself and params
is equal to ht->p, there is no point in checking params.key_len
and falling back to ht->p.key_len.
For some reason gcc couldn't figure out that params is the same
as ht->p. So let's help it by only checking params.key_len when
it's a constant.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
A comment to document this gcc hack would be nice as it's not
obvious from just reading the code. Shouldn't hold up this series
though.
Acked-by: Thomas Graf <tgraf@suug.ch>
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-22 11:55:44
On 03/22/15 at 07:04pm, Herbert Xu wrote:
This patch removes the explicit jhash value for the hashfn parameter
of rhashtable. The default is now jhash so removing the setting
makes no difference apart from making one less copy of jhash in
the kernel.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-22 11:56:14
On 03/22/15 at 07:04pm, Herbert Xu wrote:
This patch removes the explicit jhash value for the hashfn parameter
of rhashtable. The default is now jhash so removing the setting
makes no difference apart from making one less copy of jhash in
the kernel.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-22 11:57:00
On 03/22/15 at 07:04pm, Herbert Xu wrote:
This patch removes the explicit jhash value for the hashfn parameter
of rhashtable. The default is now jhash so removing the setting
makes no difference apart from making one less copy of jhash in
the kernel.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Looks likes this is not needed given Patrick's work.
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-22 12:17:57
On 03/22/15 at 07:04pm, Herbert Xu wrote:
Automatic shrinking is dangerous because it provides an easy
way for an adversary to cause us to do unnecessary work. Thus
making the resizable hashtable a poor data structure.
This patch disables automatic shrinking but retains a manual
shrink function for those cases where insertions and removals
are overseen by a trusted entity, e.g., nft_hash.
This is misleading. I agree that unconditional shrinking is dangerous.
Shrinking was an optional feature disabled by default before. The
inlining enabled it by default for all users. What is the benefit of
requiring this logic outside of rhashtable over just adding a flag to
enable shrinking at 30% utilization?
The shrink function will now also shrink to fit rather than halve
the size of the table.
If rhashtable_shrink() is called near the 75% border it will cause an
immediate expansion again. Maybe make this * 3 / 2 so we shrink near
30% utilization as before?
Why is this needed? It looks like you're always initializing this
with ht->p.key_len
It's ht->p.key_len/4 if we use jhash2.
Sure but why not just store key_len/4 in ht->p.key_len then if you
opt in to jhash2() in rhashtable_init()?
quoted
quoted
+ if (!__builtin_constant_p(params.key_len))
+ hash = ht->p.hashfn(key, ht->key_len, tbl->hash_rnd);
I don't understand this. It looks like you only consider
params->key_len if it's constant.
If params->key_len is not constant, then params == ht->p.
I must be missing something obvious. Who guarantees that? I can see
that's true for the current callers but what prevents anybody from
using rhashtable_lookup_fast() with a key length not known at compile
time and pass it as rhashtable_params?
I found the check further down. Any particular reason why check
after allocation and then free again? Why do you want to avoid
the allocation inside the mutex?
Why is this needed? It looks like you're always initializing this
with ht->p.key_len
It's ht->p.key_len/4 if we use jhash2.
Sure but why not just store key_len/4 in ht->p.key_len then if you
opt in to jhash2() in rhashtable_init()?
Because that breaks rhashtable_compare/memcmp.
quoted
quoted
quoted
+ if (!__builtin_constant_p(params.key_len))
+ hash = ht->p.hashfn(key, ht->key_len, tbl->hash_rnd);
I don't understand this. It looks like you only consider
params->key_len if it's constant.
If params->key_len is not constant, then params == ht->p.
I must be missing something obvious. Who guarantees that? I can see
that's true for the current callers but what prevents anybody from
using rhashtable_lookup_fast() with a key length not known at compile
time and pass it as rhashtable_params?
They shouldn't be doing that. The whole point of this function
is to have it inlined so all external callers of it should be
supplying a constant parameter. We could add a __ variant that
is only called by rhashtable if you like so we can enforce this
in rhashtable_lookup_fast.
I still don't get this. Why do we fall back to jhash2() if
params.key_len is set but not if only ht->p.key_len is set?
Because if params.key_len is not set then we have no idea whether
we should use jhash or jhash2 because ht->p.key_len cannot be
known at compile time. This is only used by netfilter currently
as it has a key-length set at run-time.
Cheers,
--
Email: Herbert Xu [off-list ref]
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
I found the check further down. Any particular reason why check
after allocation and then free again? Why do you want to avoid
the allocation inside the mutex?
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-23 00:10:10
On Sun, Mar 22, 2015 at 12:17:55PM +0000, Thomas Graf wrote:
This is misleading. I agree that unconditional shrinking is dangerous.
Shrinking was an optional feature disabled by default before. The
How was shrinking disabled before? AFAICS it always kicked in at
30%.
inlining enabled it by default for all users. What is the benefit of
requiring this logic outside of rhashtable over just adding a flag to
enable shrinking at 30% utilization?
That would be adding an extra branch on the fast-path for an
operation which almost nobody needs.
If rhashtable_shrink() is called near the 75% border it will cause an
immediate expansion again. Maybe make this * 3 / 2 so we shrink near
30% utilization as before?
From: Simon Horman <hidden> Date: 2015-03-23 01:18:32
Hi Herbert,
On Sun, Mar 22, 2015 at 07:04:02PM +1100, Herbert Xu wrote:
quoted hunk
This patch removes the explicit jhash value for the hashfn parameter
of rhashtable. The default is now jhash so removing the setting
makes no difference apart from making one less copy of jhash in
the kernel.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
---
net/netlink/af_netlink.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
I understand the above change in the context of the rest of the series,
however, it does not seem to match up with the changelog for this patch.
}
static const struct rhashtable_params netlink_rhashtable_params = {
.head_offset = offsetof(struct netlink_sock, node),
.key_len = netlink_compare_arg_len,
- .hashfn = jhash,
.obj_hashfn = netlink_hash,
.obj_cmpfn = netlink_compare,
.max_size = 65536,
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-23 08:33:24
On 03/23/15 at 11:09am, Herbert Xu wrote:
On Sun, Mar 22, 2015 at 12:17:55PM +0000, Thomas Graf wrote:
quoted
This is misleading. I agree that unconditional shrinking is dangerous.
Shrinking was an optional feature disabled by default before. The
How was shrinking disabled before? AFAICS it always kicked in at
30%.
Before Daniel removed the indirection due to all callers enabling
shrinking by default ;-) It was clear that some future users
eventually would not want shrinking and thus require a conditional.
quoted
inlining enabled it by default for all users. What is the benefit of
requiring this logic outside of rhashtable over just adding a flag to
enable shrinking at 30% utilization?
That would be adding an extra branch on the fast-path for an
operation which almost nobody needs.
I don't get why almost nobody would want shrinking. I agree that for
tables like TCP hash tables, once you have grown you want to keep that
table size because the load is likely to come back. But we will also
have lots of users such as the Netlink socket with a table per protocol
where not shrinking results in giving the user the ability to waste
memory indefinitely for no gain.
I'm not claiming you always want shrinking but what gain is there by
removing integrated support? Can you show numbers that the additional
branch actually hurts?
I found the check further down. Any particular reason why check
after allocation and then free again? Why do you want to avoid
the allocation inside the mutex?
It's just quality of code. You should always try to minimise
the locked sections.
So do you expect the user to replicate the new table size calculation
outside of rhashtable_shrink() to avoid the cost of a possible massive
memory allocation even if no shrinking will take place?
I think rhashtable_shrink() should fetch ht->tbl in an RCU section to
cheaply get the current table size and only do the allocation and take
the lock if the table size warrants for shrinking.
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-23 09:28:35
On Mon, Mar 23, 2015 at 08:33:19AM +0000, Thomas Graf wrote:
I'm not claiming you always want shrinking but what gain is there by
removing integrated support? Can you show numbers that the additional
branch actually hurts?
You never want automatic shrinking unless all your users are
trusted. I doubt there would be many rhashtable users where
this would apply. Even nft_hash is quite tenuous.
Besdies, if you really want automatic shrinking, you could always
do it in the caller of rhashtable_remove. That way only you
would pay for the cost and not everybody else.
Cheers,
--
Email: Herbert Xu [off-list ref]
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-23 09:29:38
On Mon, Mar 23, 2015 at 08:37:12AM +0000, Thomas Graf wrote:
I think rhashtable_shrink() should fetch ht->tbl in an RCU section to
cheaply get the current table size and only do the allocation and take
the lock if the table size warrants for shrinking.
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-23 09:36:34
On 03/23/15 at 08:28pm, Herbert Xu wrote:
On Mon, Mar 23, 2015 at 08:33:19AM +0000, Thomas Graf wrote:
quoted
I'm not claiming you always want shrinking but what gain is there by
removing integrated support? Can you show numbers that the additional
branch actually hurts?
You never want automatic shrinking unless all your users are
trusted. I doubt there would be many rhashtable users where
this would apply. Even nft_hash is quite tenuous.
Why?
Besdies, if you really want automatic shrinking, you could always
do it in the caller of rhashtable_remove. That way only you
would pay for the cost and not everybody else.
Same can be said for growing. Why do we differ between the two?
Would you expect users requiring shrinking() to call
rhashtable_shrink() after every remove? Should they encode their
own logic based on rhashtable internals?
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-23 09:40:10
On Mon, Mar 23, 2015 at 09:36:32AM +0000, Thomas Graf wrote:
quoted
You never want automatic shrinking unless all your users are
trusted. I doubt there would be many rhashtable users where
this would apply. Even nft_hash is quite tenuous.
Why?
Because with multiple rehashing it's quite easy to convert your
hash table into a linked list by repeatedly growing and shrinking.
Multiple rehashing simply cannot work unless you get rid of automatic
shrinking for the untrusted case.
Same can be said for growing. Why do we differ between the two?
Would you expect users requiring shrinking() to call
rhashtable_shrink() after every remove? Should they encode their
own logic based on rhashtable internals?
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-23 09:43:22
On 03/23/15 at 08:29pm, Herbert Xu wrote:
On Mon, Mar 23, 2015 at 08:37:12AM +0000, Thomas Graf wrote:
quoted
I think rhashtable_shrink() should fetch ht->tbl in an RCU section to
cheaply get the current table size and only do the allocation and take
the lock if the table size warrants for shrinking.
Well you should never invoke rhashtable_shrink unless you actually
wanted to shrink. So this is something that you should have checked
before rhashtable_shrink is called.
How? The calculation of the table size is embedded in
rhashtable_shrink(). Should every user have a copy of that
calculation algorithm?
Why not just:
unlikely(ht->p.shrink && rht_shrink_below_30(..))
If you really care about that additional conditional we
can also add:
static inline int rhashtable_remove_and_shrink()
{
int err;
rcu_read_lock();
tbl = rht_dereference_rcu(ht->tbl, ht);
err = rhashtable_remove_fast();
if (unlikely(!err && rht_shrink_below_30(ht, tbl)))
schedule_work(&ht->run_work);
rcu_read_unlock();
return err;
}
I just think it's wrong to rip out all the shrinking logic and
require every single user to re-add its own copy.
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-23 09:45:16
On Mon, Mar 23, 2015 at 08:39:52PM +1100, Herbert Xu wrote:
Because with multiple rehashing it's quite easy to convert your
hash table into a linked list by repeatedly growing and shrinking.
Multiple rehashing simply cannot work unless you get rid of automatic
shrinking for the untrusted case.
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-23 09:58:44
On 03/23/15 at 08:12am, Herbert Xu wrote:
On Sun, Mar 22, 2015 at 12:32:57PM +0000, Thomas Graf wrote:
quoted
Sure but why not just store key_len/4 in ht->p.key_len then if you
opt in to jhash2() in rhashtable_init()?
Because that breaks rhashtable_compare/memcmp.
Thanks. Didn't see that.
quoted
quoted
quoted
quoted
+ if (!__builtin_constant_p(params.key_len))
+ hash = ht->p.hashfn(key, ht->key_len, tbl->hash_rnd);
I don't understand this. It looks like you only consider
params->key_len if it's constant.
If params->key_len is not constant, then params == ht->p.
I must be missing something obvious. Who guarantees that? I can see
that's true for the current callers but what prevents anybody from
using rhashtable_lookup_fast() with a key length not known at compile
time and pass it as rhashtable_params?
They shouldn't be doing that. The whole point of this function
is to have it inlined so all external callers of it should be
supplying a constant parameter. We could add a __ variant that
is only called by rhashtable if you like so we can enforce this
in rhashtable_lookup_fast.
If you add such constraints it must be clearly documented. There
is no way of figuring this out right now without reading the entire
rhashtable code (and talking to you).
quoted
I still don't get this. Why do we fall back to jhash2() if
params.key_len is set but not if only ht->p.key_len is set?
Because if params.key_len is not set then we have no idea whether
we should use jhash or jhash2 because ht->p.key_len cannot be
known at compile time. This is only used by netfilter currently
as it has a key-length set at run-time.
Sorry, still not getting this ;-)
nft_hash sets key_len to set->klen and passes it to rhashtable_init().
rhashtable_init() should then fall back to jhash() or jhash2() if no
hashfn is provided. Why is the logic in rht_key_hashfn() different?
Actually, in which case is ht->p.hashfn not set in rht_key_hashfn()?
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-23 10:08:53
On 03/23/15 at 08:44pm, Herbert Xu wrote:
On Mon, Mar 23, 2015 at 08:39:52PM +1100, Herbert Xu wrote:
quoted
Because with multiple rehashing it's quite easy to convert your
hash table into a linked list by repeatedly growing and shrinking.
Multiple rehashing simply cannot work unless you get rid of automatic
shrinking for the untrusted case.
Actually what I could do is allow automatic shrinking when there
are no outstanding rehashes. So maybe we could restore this feature
after all.
OK. Maybe this patch should be posted in the context of enabling
multiple rehashes then. It is difficult to review without having
the full context. This correlation was not clear to me from the
commit message.
I have yet to understand the implications of multiple rehashes.
The idea of having to traverse N tables for each insert, removal
and lookup in a pressure situation is still frightening.
I would like to compare it with an exponential growing logic.
Eventually both approaches can be combined to limit the chain
length of rehashes.
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-23 10:18:54
On Mon, Mar 23, 2015 at 09:58:42AM +0000, Thomas Graf wrote:
nft_hash sets key_len to set->klen and passes it to rhashtable_init().
rhashtable_init() should then fall back to jhash() or jhash2() if no
hashfn is provided. Why is the logic in rht_key_hashfn() different?
Actually, in which case is ht->p.hashfn not set in rht_key_hashfn()?
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-23 10:19:52
On Mon, Mar 23, 2015 at 10:08:52AM +0000, Thomas Graf wrote:
I have yet to understand the implications of multiple rehashes.
The idea of having to traverse N tables for each insert, removal
and lookup in a pressure situation is still frightening.
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-23 11:33:16
On Sun, Mar 22, 2015 at 11:56:59AM +0000, Thomas Graf wrote:
On 03/22/15 at 07:04pm, Herbert Xu wrote:
quoted
This patch removes the explicit jhash value for the hashfn parameter
of rhashtable. The default is now jhash so removing the setting
makes no difference apart from making one less copy of jhash in
the kernel.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Looks likes this is not needed given Patrick's work.
From: David Laight <hidden> Date: 2015-03-23 14:31:21
From: Herbert Xu
Since every current rhashtable user uses jhash as their hash
function, the fact that jhash is an inline function causes each
user to generate a copy of its code.
This function provides a solution to this problem by allowing
hashfn to be unset. In which case rhashtable will automatically
set it to jhash. Furthermore, if the key length is a multiple
of 4, we will switch over to jhash2.
Would it make sense to do this as a run-time check for the NULL
function pointer so that the jhash code itself can be inlined?
The cost of the test is likely to be less that the indirect call.
David
From: David Miller <davem@davemloft.net> Date: 2015-03-23 16:44:37
From: Thomas Graf <tgraf@suug.ch>
Date: Mon, 23 Mar 2015 08:33:19 +0000
I don't get why almost nobody would want shrinking. I agree that for
tables like TCP hash tables, once you have grown you want to keep that
table size because the load is likely to come back. But we will also
have lots of users such as the Netlink socket with a table per protocol
where not shrinking results in giving the user the ability to waste
memory indefinitely for no gain.
The user can't do this with TCP? Why is netlink only susceptible?
The only plausible argument for shrinking I've ever heard of is the
nft_hash case, and there that code can _explicitly_ ask for a shrink
after it has made a major table modification.
That puts all of the smarts for when to shrink where the knowledge
resides, and in this case that's the user.
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-03-23 21:48:34
On Mon, Mar 23, 2015 at 12:44:34PM -0400, David Miller wrote:
The user can't do this with TCP? Why is netlink only susceptible?
The only plausible argument for shrinking I've ever heard of is the
nft_hash case, and there that code can _explicitly_ ask for a shrink
after it has made a major table modification.
That puts all of the smarts for when to shrink where the knowledge
resides, and in this case that's the user.
One thing I got to say is that automatic shrinking is a really
good stress test as reenabling it allowed me to quickly identify
two bugs in the final patch :)
Other than that I totally agree that it should be disabled by
default as otherwise it increases the amortised cost of the hash
table for a paltry saving in memory.
We can do it afterwards once we're sure this whole thing is stable.
Cheers,
--
Email: Herbert Xu [off-list ref]
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
From: Thomas Graf <tgraf@suug.ch> Date: 2015-03-23 22:13:39
On 03/23/15 at 12:44pm, David Miller wrote:
From: Thomas Graf <tgraf@suug.ch>
Date: Mon, 23 Mar 2015 08:33:19 +0000
quoted
I don't get why almost nobody would want shrinking. I agree that for
tables like TCP hash tables, once you have grown you want to keep that
table size because the load is likely to come back. But we will also
have lots of users such as the Netlink socket with a table per protocol
where not shrinking results in giving the user the ability to waste
memory indefinitely for no gain.
The user can't do this with TCP? Why is netlink only susceptible?
You are right. Any table that doesn't shrink will eventually waste
memory. I used TCP vs Netlink because I believe it represents the
difference in priorities very well. TCP may go 0..1M flows within
a fraction of a second so if you've seen that many flows before you
might get hit again and you prioritize the "being ready" to handle it
higher than eventually wasting the memory indefinitely.
Whereas with Netlink it seems (glad to be proven wrong) that the
need for instant growth is lesser as it takes time to create 1M
sockets across many PIDs. So we gain something by releasing the
resources if not needed.
The only plausible argument for shrinking I've ever heard of is the
nft_hash case, and there that code can _explicitly_ ask for a shrink
after it has made a major table modification.
That puts all of the smarts for when to shrink where the knowledge
resides, and in this case that's the user.
My argument pro automatic shrinking is simplicity. I'm absolutely
fine with disabling it by default and to require enabling it
explicitly.