dev: use name hash for dev_seq_ops

Instead of using the dev->next chain and trying to resync at each call to
dev_seq_start, use the name hash, keeping the bucket and the offset in
seq->private field.

Tests revealed the following results for ifconfig > /dev/null
	* 1000 interfaces:
		* 0.114s without patch
		* 0.089s with patch
	* 3000 interfaces:
		* 0.489s without patch
		* 0.110s with patch
	* 5000 interfaces:
		* 1.363s without patch
		* 0.250s with patch
	* 128000 interfaces (other setup):
		* ~100s without patch
		* ~30s with patch

Signed-off-by: Mihai Maruseac <mmaruseac@ixiacom.com>
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Mihai Maruseac 2011-10-20 20:45:10 +00:00 committed by David S. Miller
parent e09eff7fc1
commit f04565ddf5

View File

@ -4093,6 +4093,60 @@ static int dev_ifconf(struct net *net, char __user *arg)
} }
#ifdef CONFIG_PROC_FS #ifdef CONFIG_PROC_FS
#define BUCKET_SPACE (32 - NETDEV_HASHBITS)
struct dev_iter_state {
struct seq_net_private p;
unsigned int pos; /* bucket << BUCKET_SPACE + offset */
};
#define get_bucket(x) ((x) >> BUCKET_SPACE)
#define get_offset(x) ((x) & ((1 << BUCKET_SPACE) - 1))
#define set_bucket_offset(b, o) ((b) << BUCKET_SPACE | (o))
static inline struct net_device *dev_from_same_bucket(struct seq_file *seq)
{
struct dev_iter_state *state = seq->private;
struct net *net = seq_file_net(seq);
struct net_device *dev;
struct hlist_node *p;
struct hlist_head *h;
unsigned int count, bucket, offset;
bucket = get_bucket(state->pos);
offset = get_offset(state->pos);
h = &net->dev_name_head[bucket];
count = 0;
hlist_for_each_entry_rcu(dev, p, h, name_hlist) {
if (count++ == offset) {
state->pos = set_bucket_offset(bucket, count);
return dev;
}
}
return NULL;
}
static inline struct net_device *dev_from_new_bucket(struct seq_file *seq)
{
struct dev_iter_state *state = seq->private;
struct net_device *dev;
unsigned int bucket;
bucket = get_bucket(state->pos);
do {
dev = dev_from_same_bucket(seq);
if (dev)
return dev;
bucket++;
state->pos = set_bucket_offset(bucket, 0);
} while (bucket < NETDEV_HASHENTRIES);
return NULL;
}
/* /*
* This is invoked by the /proc filesystem handler to display a device * This is invoked by the /proc filesystem handler to display a device
* in detail. * in detail.
@ -4100,33 +4154,33 @@ static int dev_ifconf(struct net *net, char __user *arg)
void *dev_seq_start(struct seq_file *seq, loff_t *pos) void *dev_seq_start(struct seq_file *seq, loff_t *pos)
__acquires(RCU) __acquires(RCU)
{ {
struct net *net = seq_file_net(seq); struct dev_iter_state *state = seq->private;
loff_t off;
struct net_device *dev;
rcu_read_lock(); rcu_read_lock();
if (!*pos) if (!*pos)
return SEQ_START_TOKEN; return SEQ_START_TOKEN;
off = 1; /* check for end of the hash */
for_each_netdev_rcu(net, dev) if (state->pos == 0 && *pos > 1)
if (off++ == *pos) return NULL;
return dev;
return NULL; return dev_from_new_bucket(seq);
} }
void *dev_seq_next(struct seq_file *seq, void *v, loff_t *pos) void *dev_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{ {
struct net_device *dev = v; struct net_device *dev;
if (v == SEQ_START_TOKEN)
dev = first_net_device_rcu(seq_file_net(seq));
else
dev = next_net_device_rcu(dev);
++*pos; ++*pos;
return dev;
if (v == SEQ_START_TOKEN)
return dev_from_new_bucket(seq);
dev = dev_from_same_bucket(seq);
if (dev)
return dev;
return dev_from_new_bucket(seq);
} }
void dev_seq_stop(struct seq_file *seq, void *v) void dev_seq_stop(struct seq_file *seq, void *v)
@ -4225,7 +4279,7 @@ static const struct seq_operations dev_seq_ops = {
static int dev_seq_open(struct inode *inode, struct file *file) static int dev_seq_open(struct inode *inode, struct file *file)
{ {
return seq_open_net(inode, file, &dev_seq_ops, return seq_open_net(inode, file, &dev_seq_ops,
sizeof(struct seq_net_private)); sizeof(struct dev_iter_state));
} }
static const struct file_operations dev_seq_fops = { static const struct file_operations dev_seq_fops = {