]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
igmp: hash a hash table to speedup ip_check_mc_rcu()
authorEric Dumazet <edumazet@google.com>
Fri, 7 Jun 2013 15:48:57 +0000 (08:48 -0700)
committerDavid S. Miller <davem@davemloft.net>
Wed, 12 Jun 2013 07:25:23 +0000 (00:25 -0700)
After IP route cache removal, multicast applications using
a lot of multicast addresses hit a O(N) behavior in ip_check_mc_rcu()

Add a per in_device hash table to get faster lookup.

This hash table is created only if the number of items in mc_list is
above 4.

Reported-by: Shawn Bohrer <sbohrer@rgmadvisors.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
Tested-by: Shawn Bohrer <sbohrer@rgmadvisors.com>
Reviewed-by: Cong Wang <xiyou.wangcong@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/igmp.h
include/linux/inetdevice.h
net/ipv4/devinet.c
net/ipv4/igmp.c

index 7f2bf15184802b12733489b36755fce4e9c2caf4..e3362b5f13e8258857f50a64e73fe3e70f9039f3 100644 (file)
@@ -84,6 +84,7 @@ struct ip_mc_list {
                struct ip_mc_list *next;
                struct ip_mc_list __rcu *next_rcu;
        };
+       struct ip_mc_list __rcu *next_hash;
        struct timer_list       timer;
        int                     users;
        atomic_t                refcnt;
index ea1e3b8638900a5d7f5cf46e49eec37c3274f9a9..b99cd23f347489ea0a3c2a14d58193c6e1117331 100644 (file)
@@ -50,12 +50,17 @@ struct ipv4_devconf {
        DECLARE_BITMAP(state, IPV4_DEVCONF_MAX);
 };
 
+#define MC_HASH_SZ_LOG 9
+
 struct in_device {
        struct net_device       *dev;
        atomic_t                refcnt;
        int                     dead;
        struct in_ifaddr        *ifa_list;      /* IP ifaddr chain              */
+
        struct ip_mc_list __rcu *mc_list;       /* IP multicast filter chain    */
+       struct ip_mc_list __rcu * __rcu *mc_hash;
+
        int                     mc_count;       /* Number of installed mcasts   */
        spinlock_t              mc_tomb_lock;
        struct ip_mc_list       *mc_tomb;
index b047e2d8a614f2ee561f0741687d058b0deafa06..3469506c106d41e15107f7453578ed21c13f929b 100644 (file)
@@ -215,6 +215,7 @@ void in_dev_finish_destroy(struct in_device *idev)
 
        WARN_ON(idev->ifa_list);
        WARN_ON(idev->mc_list);
+       kfree(rcu_dereference_protected(idev->mc_hash, 1));
 #ifdef NET_REFCNT_DEBUG
        pr_debug("%s: %p=%s\n", __func__, idev, dev ? dev->name : "NIL");
 #endif
index 450f625361e4bf181f2ffab6137a57668e8d9eea..f72011df9c5927ca0308bfa5c8c00d690558bdea 100644 (file)
@@ -1217,6 +1217,57 @@ static void igmp_group_added(struct ip_mc_list *im)
  *     Multicast list managers
  */
 
+static u32 ip_mc_hash(const struct ip_mc_list *im)
+{
+       return hash_32((u32)im->multiaddr, MC_HASH_SZ_LOG);
+}
+
+static void ip_mc_hash_add(struct in_device *in_dev,
+                          struct ip_mc_list *im)
+{
+       struct ip_mc_list __rcu **mc_hash;
+       u32 hash;
+
+       mc_hash = rtnl_dereference(in_dev->mc_hash);
+       if (mc_hash) {
+               hash = ip_mc_hash(im);
+               im->next_hash = rtnl_dereference(mc_hash[hash]);
+               rcu_assign_pointer(mc_hash[hash], im);
+               return;
+       }
+
+       /* do not use a hash table for small number of items */
+       if (in_dev->mc_count < 4)
+               return;
+
+       mc_hash = kzalloc(sizeof(struct ip_mc_list *) << MC_HASH_SZ_LOG,
+                         GFP_KERNEL);
+       if (!mc_hash)
+               return;
+
+       for_each_pmc_rtnl(in_dev, im) {
+               hash = ip_mc_hash(im);
+               im->next_hash = rtnl_dereference(mc_hash[hash]);
+               RCU_INIT_POINTER(mc_hash[hash], im);
+       }
+
+       rcu_assign_pointer(in_dev->mc_hash, mc_hash);
+}
+
+static void ip_mc_hash_remove(struct in_device *in_dev,
+                             struct ip_mc_list *im)
+{
+       struct ip_mc_list __rcu **mc_hash = rtnl_dereference(in_dev->mc_hash);
+       struct ip_mc_list *aux;
+
+       if (!mc_hash)
+               return;
+       mc_hash += ip_mc_hash(im);
+       while ((aux = rtnl_dereference(*mc_hash)) != im)
+               mc_hash = &aux->next_hash;
+       *mc_hash = im->next_hash;
+}
+
 
 /*
  *     A socket has joined a multicast group on device dev.
@@ -1258,6 +1309,8 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
        in_dev->mc_count++;
        rcu_assign_pointer(in_dev->mc_list, im);
 
+       ip_mc_hash_add(in_dev, im);
+
 #ifdef CONFIG_IP_MULTICAST
        igmpv3_del_delrec(in_dev, im->multiaddr);
 #endif
@@ -1314,6 +1367,7 @@ void ip_mc_dec_group(struct in_device *in_dev, __be32 addr)
             ip = &i->next_rcu) {
                if (i->multiaddr == addr) {
                        if (--i->users == 0) {
+                               ip_mc_hash_remove(in_dev, i);
                                *ip = i->next_rcu;
                                in_dev->mc_count--;
                                igmp_group_dropped(i);
@@ -2321,12 +2375,25 @@ void ip_mc_drop_socket(struct sock *sk)
 int ip_check_mc_rcu(struct in_device *in_dev, __be32 mc_addr, __be32 src_addr, u16 proto)
 {
        struct ip_mc_list *im;
+       struct ip_mc_list __rcu **mc_hash;
        struct ip_sf_list *psf;
        int rv = 0;
 
-       for_each_pmc_rcu(in_dev, im) {
-               if (im->multiaddr == mc_addr)
-                       break;
+       mc_hash = rcu_dereference(in_dev->mc_hash);
+       if (mc_hash) {
+               u32 hash = hash_32((u32)mc_addr, MC_HASH_SZ_LOG);
+
+               for (im = rcu_dereference(mc_hash[hash]);
+                    im != NULL;
+                    im = rcu_dereference(im->next_hash)) {
+                       if (im->multiaddr == mc_addr)
+                               break;
+               }
+       } else {
+               for_each_pmc_rcu(in_dev, im) {
+                       if (im->multiaddr == mc_addr)
+                               break;
+               }
        }
        if (im && proto == IPPROTO_IGMP) {
                rv = 1;