Commit dbca9b27 authored by Eric Dumazet's avatar Eric Dumazet Committed by David S. Miller

[NET]: change layout of ehash table

ehash table layout is currently this one :

First half of this table is used by sockets not in TIME_WAIT state
Second half of it is used by sockets in TIME_WAIT state.

This is non optimal because of for a given hash or socket, the two chain heads 
are located in separate cache lines.
Moreover the locks of the second half are never used.

If instead of this halving, we use two list heads in inet_ehash_bucket instead 
of only one, we probably can avoid one cache miss, and reduce ram usage, 
particularly if sizeof(rwlock_t) is big (various CONFIG_DEBUG_SPINLOCK, 
CONFIG_DEBUG_LOCK_ALLOC settings). So we still halves the table but we keep 
together related chains to speedup lookups and socket state change.

In this patch I did not try to align struct inet_ehash_bucket, but a future 
patch could try to make this structure have a convenient size (a power of two 
or a multiple of L1_CACHE_SIZE).
I guess rwlock will just vanish as soon as RCU is plugged into ehash :) , so 
maybe we dont need to scratch our heads to align the bucket...

Note : In case struct inet_ehash_bucket is not a power of two, we could 
probably change alloc_large_system_hash() (in case it use __get_free_pages()) 
to free the unused space. It currently allocates a big zone, but the last 
quarter of it could be freed. Again, this should be a temporary 'problem'.

Patch tested on ipv4 tcp only, but should be OK for IPV6 and DCCP.
Signed-off-by: default avatarEric Dumazet <dada1@cosmosbay.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent eac3731b
...@@ -34,12 +34,13 @@ ...@@ -34,12 +34,13 @@
#include <asm/byteorder.h> #include <asm/byteorder.h>
/* This is for all connections with a full identity, no wildcards. /* This is for all connections with a full identity, no wildcards.
* New scheme, half the table is for TIME_WAIT, the other half is * One chain is dedicated to TIME_WAIT sockets.
* for the rest. I'll experiment with dynamic table growth later. * I'll experiment with dynamic table growth later.
*/ */
struct inet_ehash_bucket { struct inet_ehash_bucket {
rwlock_t lock; rwlock_t lock;
struct hlist_head chain; struct hlist_head chain;
struct hlist_head twchain;
}; };
/* There are a few simple rules, which allow for local port reuse by /* There are a few simple rules, which allow for local port reuse by
...@@ -97,8 +98,7 @@ struct inet_hashinfo { ...@@ -97,8 +98,7 @@ struct inet_hashinfo {
* *
* TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE * TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE
* *
* First half of the table is for sockets not in TIME_WAIT, second half * TIME_WAIT sockets use a separate chain (twchain).
* is for TIME_WAIT sockets only.
*/ */
struct inet_ehash_bucket *ehash; struct inet_ehash_bucket *ehash;
...@@ -369,7 +369,7 @@ static inline struct sock * ...@@ -369,7 +369,7 @@ static inline struct sock *
} }
/* Must check for a TIME_WAIT'er before going to listener hash. */ /* Must check for a TIME_WAIT'er before going to listener hash. */
sk_for_each(sk, node, &(head + hashinfo->ehash_size)->chain) { sk_for_each(sk, node, &head->twchain) {
if (INET_TW_MATCH(sk, hash, acookie, saddr, daddr, ports, dif)) if (INET_TW_MATCH(sk, hash, acookie, saddr, daddr, ports, dif))
goto hit; goto hit;
} }
......
...@@ -1024,7 +1024,6 @@ static int __init dccp_init(void) ...@@ -1024,7 +1024,6 @@ static int __init dccp_init(void)
do { do {
dccp_hashinfo.ehash_size = (1UL << ehash_order) * PAGE_SIZE / dccp_hashinfo.ehash_size = (1UL << ehash_order) * PAGE_SIZE /
sizeof(struct inet_ehash_bucket); sizeof(struct inet_ehash_bucket);
dccp_hashinfo.ehash_size >>= 1;
while (dccp_hashinfo.ehash_size & while (dccp_hashinfo.ehash_size &
(dccp_hashinfo.ehash_size - 1)) (dccp_hashinfo.ehash_size - 1))
dccp_hashinfo.ehash_size--; dccp_hashinfo.ehash_size--;
...@@ -1037,9 +1036,10 @@ static int __init dccp_init(void) ...@@ -1037,9 +1036,10 @@ static int __init dccp_init(void)
goto out_free_bind_bucket_cachep; goto out_free_bind_bucket_cachep;
} }
for (i = 0; i < (dccp_hashinfo.ehash_size << 1); i++) { for (i = 0; i < dccp_hashinfo.ehash_size; i++) {
rwlock_init(&dccp_hashinfo.ehash[i].lock); rwlock_init(&dccp_hashinfo.ehash[i].lock);
INIT_HLIST_HEAD(&dccp_hashinfo.ehash[i].chain); INIT_HLIST_HEAD(&dccp_hashinfo.ehash[i].chain);
INIT_HLIST_HEAD(&dccp_hashinfo.ehash[i].twchain);
} }
bhash_order = ehash_order; bhash_order = ehash_order;
......
...@@ -778,7 +778,7 @@ static int inet_diag_dump(struct sk_buff *skb, struct netlink_callback *cb) ...@@ -778,7 +778,7 @@ static int inet_diag_dump(struct sk_buff *skb, struct netlink_callback *cb)
struct inet_timewait_sock *tw; struct inet_timewait_sock *tw;
inet_twsk_for_each(tw, node, inet_twsk_for_each(tw, node,
&hashinfo->ehash[i + hashinfo->ehash_size].chain) { &head->twchain) {
if (num < s_num) if (num < s_num)
goto next_dying; goto next_dying;
......
...@@ -212,7 +212,7 @@ static int __inet_check_established(struct inet_timewait_death_row *death_row, ...@@ -212,7 +212,7 @@ static int __inet_check_established(struct inet_timewait_death_row *death_row,
write_lock(&head->lock); write_lock(&head->lock);
/* Check TIME-WAIT sockets first. */ /* Check TIME-WAIT sockets first. */
sk_for_each(sk2, node, &(head + hinfo->ehash_size)->chain) { sk_for_each(sk2, node, &head->twchain) {
tw = inet_twsk(sk2); tw = inet_twsk(sk2);
if (INET_TW_MATCH(sk2, hash, acookie, saddr, daddr, ports, dif)) { if (INET_TW_MATCH(sk2, hash, acookie, saddr, daddr, ports, dif)) {
......
...@@ -78,8 +78,8 @@ void __inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk, ...@@ -78,8 +78,8 @@ void __inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,
if (__sk_del_node_init(sk)) if (__sk_del_node_init(sk))
sock_prot_dec_use(sk->sk_prot); sock_prot_dec_use(sk->sk_prot);
/* Step 3: Hash TW into TIMEWAIT half of established hash table. */ /* Step 3: Hash TW into TIMEWAIT chain. */
inet_twsk_add_node(tw, &(ehead + hashinfo->ehash_size)->chain); inet_twsk_add_node(tw, &ehead->twchain);
atomic_inc(&tw->tw_refcnt); atomic_inc(&tw->tw_refcnt);
write_unlock(&ehead->lock); write_unlock(&ehead->lock);
......
...@@ -2415,10 +2415,11 @@ void __init tcp_init(void) ...@@ -2415,10 +2415,11 @@ void __init tcp_init(void)
&tcp_hashinfo.ehash_size, &tcp_hashinfo.ehash_size,
NULL, NULL,
0); 0);
tcp_hashinfo.ehash_size = (1 << tcp_hashinfo.ehash_size) >> 1; tcp_hashinfo.ehash_size = 1 << tcp_hashinfo.ehash_size;
for (i = 0; i < (tcp_hashinfo.ehash_size << 1); i++) { for (i = 0; i < tcp_hashinfo.ehash_size; i++) {
rwlock_init(&tcp_hashinfo.ehash[i].lock); rwlock_init(&tcp_hashinfo.ehash[i].lock);
INIT_HLIST_HEAD(&tcp_hashinfo.ehash[i].chain); INIT_HLIST_HEAD(&tcp_hashinfo.ehash[i].chain);
INIT_HLIST_HEAD(&tcp_hashinfo.ehash[i].twchain);
} }
tcp_hashinfo.bhash = tcp_hashinfo.bhash =
...@@ -2475,7 +2476,7 @@ void __init tcp_init(void) ...@@ -2475,7 +2476,7 @@ void __init tcp_init(void)
printk(KERN_INFO "TCP: Hash tables configured " printk(KERN_INFO "TCP: Hash tables configured "
"(established %d bind %d)\n", "(established %d bind %d)\n",
tcp_hashinfo.ehash_size << 1, tcp_hashinfo.bhash_size); tcp_hashinfo.ehash_size, tcp_hashinfo.bhash_size);
tcp_register_congestion_control(&tcp_reno); tcp_register_congestion_control(&tcp_reno);
} }
......
...@@ -2051,7 +2051,7 @@ static void *established_get_first(struct seq_file *seq) ...@@ -2051,7 +2051,7 @@ static void *established_get_first(struct seq_file *seq)
} }
st->state = TCP_SEQ_STATE_TIME_WAIT; st->state = TCP_SEQ_STATE_TIME_WAIT;
inet_twsk_for_each(tw, node, inet_twsk_for_each(tw, node,
&tcp_hashinfo.ehash[st->bucket + tcp_hashinfo.ehash_size].chain) { &tcp_hashinfo.ehash[st->bucket].twchain) {
if (tw->tw_family != st->family) { if (tw->tw_family != st->family) {
continue; continue;
} }
...@@ -2107,7 +2107,7 @@ static void *established_get_next(struct seq_file *seq, void *cur) ...@@ -2107,7 +2107,7 @@ static void *established_get_next(struct seq_file *seq, void *cur)
} }
st->state = TCP_SEQ_STATE_TIME_WAIT; st->state = TCP_SEQ_STATE_TIME_WAIT;
tw = tw_head(&tcp_hashinfo.ehash[st->bucket + tcp_hashinfo.ehash_size].chain); tw = tw_head(&tcp_hashinfo.ehash[st->bucket].twchain);
goto get_tw; goto get_tw;
found: found:
cur = sk; cur = sk;
......
...@@ -79,7 +79,7 @@ struct sock *__inet6_lookup_established(struct inet_hashinfo *hashinfo, ...@@ -79,7 +79,7 @@ struct sock *__inet6_lookup_established(struct inet_hashinfo *hashinfo,
goto hit; /* You sunk my battleship! */ goto hit; /* You sunk my battleship! */
} }
/* Must check for a TIME_WAIT'er before going to listener hash. */ /* Must check for a TIME_WAIT'er before going to listener hash. */
sk_for_each(sk, node, &(head + hashinfo->ehash_size)->chain) { sk_for_each(sk, node, &head->twchain) {
const struct inet_timewait_sock *tw = inet_twsk(sk); const struct inet_timewait_sock *tw = inet_twsk(sk);
if(*((__portpair *)&(tw->tw_dport)) == ports && if(*((__portpair *)&(tw->tw_dport)) == ports &&
...@@ -183,7 +183,7 @@ static int __inet6_check_established(struct inet_timewait_death_row *death_row, ...@@ -183,7 +183,7 @@ static int __inet6_check_established(struct inet_timewait_death_row *death_row,
write_lock(&head->lock); write_lock(&head->lock);
/* Check TIME-WAIT sockets first. */ /* Check TIME-WAIT sockets first. */
sk_for_each(sk2, node, &(head + hinfo->ehash_size)->chain) { sk_for_each(sk2, node, &head->twchain) {
const struct inet6_timewait_sock *tw6 = inet6_twsk(sk2); const struct inet6_timewait_sock *tw6 = inet6_twsk(sk2);
tw = inet_twsk(sk2); tw = inet_twsk(sk2);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment