Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[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: Eric Dumazet <dada1@cosmosbay.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Eric Dumazet and committed by
David S. Miller
dbca9b27 eac3731b

+19 -18
+5 -5
include/net/inet_hashtables.h
··· 34 34 #include <asm/byteorder.h> 35 35 36 36 /* This is for all connections with a full identity, no wildcards. 37 - * New scheme, half the table is for TIME_WAIT, the other half is 38 - * for the rest. I'll experiment with dynamic table growth later. 37 + * One chain is dedicated to TIME_WAIT sockets. 38 + * I'll experiment with dynamic table growth later. 39 39 */ 40 40 struct inet_ehash_bucket { 41 41 rwlock_t lock; 42 42 struct hlist_head chain; 43 + struct hlist_head twchain; 43 44 }; 44 45 45 46 /* There are a few simple rules, which allow for local port reuse by ··· 98 97 * 99 98 * TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE 100 99 * 101 - * First half of the table is for sockets not in TIME_WAIT, second half 102 - * is for TIME_WAIT sockets only. 100 + * TIME_WAIT sockets use a separate chain (twchain). 103 101 */ 104 102 struct inet_ehash_bucket *ehash; 105 103 ··· 369 369 } 370 370 371 371 /* Must check for a TIME_WAIT'er before going to listener hash. */ 372 - sk_for_each(sk, node, &(head + hashinfo->ehash_size)->chain) { 372 + sk_for_each(sk, node, &head->twchain) { 373 373 if (INET_TW_MATCH(sk, hash, acookie, saddr, daddr, ports, dif)) 374 374 goto hit; 375 375 }
+2 -2
net/dccp/proto.c
··· 1024 1024 do { 1025 1025 dccp_hashinfo.ehash_size = (1UL << ehash_order) * PAGE_SIZE / 1026 1026 sizeof(struct inet_ehash_bucket); 1027 - dccp_hashinfo.ehash_size >>= 1; 1028 1027 while (dccp_hashinfo.ehash_size & 1029 1028 (dccp_hashinfo.ehash_size - 1)) 1030 1029 dccp_hashinfo.ehash_size--; ··· 1036 1037 goto out_free_bind_bucket_cachep; 1037 1038 } 1038 1039 1039 - for (i = 0; i < (dccp_hashinfo.ehash_size << 1); i++) { 1040 + for (i = 0; i < dccp_hashinfo.ehash_size; i++) { 1040 1041 rwlock_init(&dccp_hashinfo.ehash[i].lock); 1041 1042 INIT_HLIST_HEAD(&dccp_hashinfo.ehash[i].chain); 1043 + INIT_HLIST_HEAD(&dccp_hashinfo.ehash[i].twchain); 1042 1044 } 1043 1045 1044 1046 bhash_order = ehash_order;
+1 -1
net/ipv4/inet_diag.c
··· 778 778 struct inet_timewait_sock *tw; 779 779 780 780 inet_twsk_for_each(tw, node, 781 - &hashinfo->ehash[i + hashinfo->ehash_size].chain) { 781 + &head->twchain) { 782 782 783 783 if (num < s_num) 784 784 goto next_dying;
+1 -1
net/ipv4/inet_hashtables.c
··· 212 212 write_lock(&head->lock); 213 213 214 214 /* Check TIME-WAIT sockets first. */ 215 - sk_for_each(sk2, node, &(head + hinfo->ehash_size)->chain) { 215 + sk_for_each(sk2, node, &head->twchain) { 216 216 tw = inet_twsk(sk2); 217 217 218 218 if (INET_TW_MATCH(sk2, hash, acookie, saddr, daddr, ports, dif)) {
+2 -2
net/ipv4/inet_timewait_sock.c
··· 78 78 if (__sk_del_node_init(sk)) 79 79 sock_prot_dec_use(sk->sk_prot); 80 80 81 - /* Step 3: Hash TW into TIMEWAIT half of established hash table. */ 82 - inet_twsk_add_node(tw, &(ehead + hashinfo->ehash_size)->chain); 81 + /* Step 3: Hash TW into TIMEWAIT chain. */ 82 + inet_twsk_add_node(tw, &ehead->twchain); 83 83 atomic_inc(&tw->tw_refcnt); 84 84 85 85 write_unlock(&ehead->lock);
+4 -3
net/ipv4/tcp.c
··· 2415 2415 &tcp_hashinfo.ehash_size, 2416 2416 NULL, 2417 2417 0); 2418 - tcp_hashinfo.ehash_size = (1 << tcp_hashinfo.ehash_size) >> 1; 2419 - for (i = 0; i < (tcp_hashinfo.ehash_size << 1); i++) { 2418 + tcp_hashinfo.ehash_size = 1 << tcp_hashinfo.ehash_size; 2419 + for (i = 0; i < tcp_hashinfo.ehash_size; i++) { 2420 2420 rwlock_init(&tcp_hashinfo.ehash[i].lock); 2421 2421 INIT_HLIST_HEAD(&tcp_hashinfo.ehash[i].chain); 2422 + INIT_HLIST_HEAD(&tcp_hashinfo.ehash[i].twchain); 2422 2423 } 2423 2424 2424 2425 tcp_hashinfo.bhash = ··· 2476 2475 2477 2476 printk(KERN_INFO "TCP: Hash tables configured " 2478 2477 "(established %d bind %d)\n", 2479 - tcp_hashinfo.ehash_size << 1, tcp_hashinfo.bhash_size); 2478 + tcp_hashinfo.ehash_size, tcp_hashinfo.bhash_size); 2480 2479 2481 2480 tcp_register_congestion_control(&tcp_reno); 2482 2481 }
+2 -2
net/ipv4/tcp_ipv4.c
··· 2051 2051 } 2052 2052 st->state = TCP_SEQ_STATE_TIME_WAIT; 2053 2053 inet_twsk_for_each(tw, node, 2054 - &tcp_hashinfo.ehash[st->bucket + tcp_hashinfo.ehash_size].chain) { 2054 + &tcp_hashinfo.ehash[st->bucket].twchain) { 2055 2055 if (tw->tw_family != st->family) { 2056 2056 continue; 2057 2057 } ··· 2107 2107 } 2108 2108 2109 2109 st->state = TCP_SEQ_STATE_TIME_WAIT; 2110 - tw = tw_head(&tcp_hashinfo.ehash[st->bucket + tcp_hashinfo.ehash_size].chain); 2110 + tw = tw_head(&tcp_hashinfo.ehash[st->bucket].twchain); 2111 2111 goto get_tw; 2112 2112 found: 2113 2113 cur = sk;
+2 -2
net/ipv6/inet6_hashtables.c
··· 79 79 goto hit; /* You sunk my battleship! */ 80 80 } 81 81 /* Must check for a TIME_WAIT'er before going to listener hash. */ 82 - sk_for_each(sk, node, &(head + hashinfo->ehash_size)->chain) { 82 + sk_for_each(sk, node, &head->twchain) { 83 83 const struct inet_timewait_sock *tw = inet_twsk(sk); 84 84 85 85 if(*((__portpair *)&(tw->tw_dport)) == ports && ··· 183 183 write_lock(&head->lock); 184 184 185 185 /* Check TIME-WAIT sockets first. */ 186 - sk_for_each(sk2, node, &(head + hinfo->ehash_size)->chain) { 186 + sk_for_each(sk2, node, &head->twchain) { 187 187 const struct inet6_timewait_sock *tw6 = inet6_twsk(sk2); 188 188 189 189 tw = inet_twsk(sk2);