diff options
| author | Thadeu Lima de Souza Cascardo <cascardo@redhat.com> | 2016-09-15 18:11:53 -0400 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2016-09-18 22:14:01 -0400 |
| commit | db74a3335e0f645e3139c80bcfc90feb01d8e304 (patch) | |
| tree | 5adec60da4faa80b188cc0b45639224c0a197db4 /net/openvswitch | |
| parent | 40773966ccf1985a1b2bb570a03cbeaf1cbd4e00 (diff) | |
openvswitch: use percpu flow stats
Instead of using flow stats per NUMA node, use it per CPU. When using
megaflows, the stats lock can be a bottleneck in scalability.
On a E5-2690 12-core system, usual throughput went from ~4Mpps to
~15Mpps when forwarding between two 40GbE ports with a single flow
configured on the datapath.
This has been tested on a system with possible CPUs 0-7,16-23. After
module removal, there were no corruption on the slab cache.
Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@redhat.com>
Cc: pravin shelar <pshelar@ovn.org>
Acked-by: Pravin B Shelar <pshelar@ovn.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/openvswitch')
| -rw-r--r-- | net/openvswitch/flow.c | 42 | ||||
| -rw-r--r-- | net/openvswitch/flow.h | 4 | ||||
| -rw-r--r-- | net/openvswitch/flow_table.c | 26 |
3 files changed, 33 insertions, 39 deletions
diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c index 5b80612df182..0fa45439def1 100644 --- a/net/openvswitch/flow.c +++ b/net/openvswitch/flow.c | |||
| @@ -29,6 +29,7 @@ | |||
| 29 | #include <linux/module.h> | 29 | #include <linux/module.h> |
| 30 | #include <linux/in.h> | 30 | #include <linux/in.h> |
| 31 | #include <linux/rcupdate.h> | 31 | #include <linux/rcupdate.h> |
| 32 | #include <linux/cpumask.h> | ||
| 32 | #include <linux/if_arp.h> | 33 | #include <linux/if_arp.h> |
| 33 | #include <linux/ip.h> | 34 | #include <linux/ip.h> |
| 34 | #include <linux/ipv6.h> | 35 | #include <linux/ipv6.h> |
| @@ -72,32 +73,33 @@ void ovs_flow_stats_update(struct sw_flow *flow, __be16 tcp_flags, | |||
| 72 | { | 73 | { |
| 73 | struct flow_stats *stats; | 74 | struct flow_stats *stats; |
| 74 | int node = numa_node_id(); | 75 | int node = numa_node_id(); |
| 76 | int cpu = smp_processor_id(); | ||
| 75 | int len = skb->len + (skb_vlan_tag_present(skb) ? VLAN_HLEN : 0); | 77 | int len = skb->len + (skb_vlan_tag_present(skb) ? VLAN_HLEN : 0); |
| 76 | 78 | ||
| 77 | stats = rcu_dereference(flow->stats[node]); | 79 | stats = rcu_dereference(flow->stats[cpu]); |
| 78 | 80 | ||
| 79 | /* Check if already have node-specific stats. */ | 81 | /* Check if already have CPU-specific stats. */ |
| 80 | if (likely(stats)) { | 82 | if (likely(stats)) { |
| 81 | spin_lock(&stats->lock); | 83 | spin_lock(&stats->lock); |
| 82 | /* Mark if we write on the pre-allocated stats. */ | 84 | /* Mark if we write on the pre-allocated stats. */ |
| 83 | if (node == 0 && unlikely(flow->stats_last_writer != node)) | 85 | if (cpu == 0 && unlikely(flow->stats_last_writer != cpu)) |
| 84 | flow->stats_last_writer = node; | 86 | flow->stats_last_writer = cpu; |
| 85 | } else { | 87 | } else { |
| 86 | stats = rcu_dereference(flow->stats[0]); /* Pre-allocated. */ | 88 | stats = rcu_dereference(flow->stats[0]); /* Pre-allocated. */ |
| 87 | spin_lock(&stats->lock); | 89 | spin_lock(&stats->lock); |
| 88 | 90 | ||
| 89 | /* If the current NUMA-node is the only writer on the | 91 | /* If the current CPU is the only writer on the |
| 90 | * pre-allocated stats keep using them. | 92 | * pre-allocated stats keep using them. |
| 91 | */ | 93 | */ |
| 92 | if (unlikely(flow->stats_last_writer != node)) { | 94 | if (unlikely(flow->stats_last_writer != cpu)) { |
| 93 | /* A previous locker may have already allocated the | 95 | /* A previous locker may have already allocated the |
| 94 | * stats, so we need to check again. If node-specific | 96 | * stats, so we need to check again. If CPU-specific |
| 95 | * stats were already allocated, we update the pre- | 97 | * stats were already allocated, we update the pre- |
| 96 | * allocated stats as we have already locked them. | 98 | * allocated stats as we have already locked them. |
| 97 | */ | 99 | */ |
| 98 | if (likely(flow->stats_last_writer != NUMA_NO_NODE) | 100 | if (likely(flow->stats_last_writer != -1) && |
| 99 | && likely(!rcu_access_pointer(flow->stats[node]))) { | 101 | likely(!rcu_access_pointer(flow->stats[cpu]))) { |
| 100 | /* Try to allocate node-specific stats. */ | 102 | /* Try to allocate CPU-specific stats. */ |
| 101 | struct flow_stats *new_stats; | 103 | struct flow_stats *new_stats; |
| 102 | 104 | ||
| 103 | new_stats = | 105 | new_stats = |
| @@ -114,12 +116,12 @@ void ovs_flow_stats_update(struct sw_flow *flow, __be16 tcp_flags, | |||
| 114 | new_stats->tcp_flags = tcp_flags; | 116 | new_stats->tcp_flags = tcp_flags; |
| 115 | spin_lock_init(&new_stats->lock); | 117 | spin_lock_init(&new_stats->lock); |
| 116 | 118 | ||
| 117 | rcu_assign_pointer(flow->stats[node], | 119 | rcu_assign_pointer(flow->stats[cpu], |
| 118 | new_stats); | 120 | new_stats); |
| 119 | goto unlock; | 121 | goto unlock; |
| 120 | } | 122 | } |
| 121 | } | 123 | } |
| 122 | flow->stats_last_writer = node; | 124 | flow->stats_last_writer = cpu; |
| 123 | } | 125 | } |
| 124 | } | 126 | } |
| 125 | 127 | ||
| @@ -136,15 +138,15 @@ void ovs_flow_stats_get(const struct sw_flow *flow, | |||
| 136 | struct ovs_flow_stats *ovs_stats, | 138 | struct ovs_flow_stats *ovs_stats, |
| 137 | unsigned long *used, __be16 *tcp_flags) | 139 | unsigned long *used, __be16 *tcp_flags) |
| 138 | { | 140 | { |
| 139 | int node; | 141 | int cpu; |
| 140 | 142 | ||
| 141 | *used = 0; | 143 | *used = 0; |
| 142 | *tcp_flags = 0; | 144 | *tcp_flags = 0; |
| 143 | memset(ovs_stats, 0, sizeof(*ovs_stats)); | 145 | memset(ovs_stats, 0, sizeof(*ovs_stats)); |
| 144 | 146 | ||
| 145 | /* We open code this to make sure node 0 is always considered */ | 147 | /* We open code this to make sure cpu 0 is always considered */ |
| 146 | for (node = 0; node < MAX_NUMNODES; node = next_node(node, node_possible_map)) { | 148 | for (cpu = 0; cpu < nr_cpu_ids; cpu = cpumask_next(cpu, cpu_possible_mask)) { |
| 147 | struct flow_stats *stats = rcu_dereference_ovsl(flow->stats[node]); | 149 | struct flow_stats *stats = rcu_dereference_ovsl(flow->stats[cpu]); |
| 148 | 150 | ||
| 149 | if (stats) { | 151 | if (stats) { |
| 150 | /* Local CPU may write on non-local stats, so we must | 152 | /* Local CPU may write on non-local stats, so we must |
| @@ -164,11 +166,11 @@ void ovs_flow_stats_get(const struct sw_flow *flow, | |||
| 164 | /* Called with ovs_mutex. */ | 166 | /* Called with ovs_mutex. */ |
| 165 | void ovs_flow_stats_clear(struct sw_flow *flow) | 167 | void ovs_flow_stats_clear(struct sw_flow *flow) |
| 166 | { | 168 | { |
| 167 | int node; | 169 | int cpu; |
| 168 | 170 | ||
| 169 | /* We open code this to make sure node 0 is always considered */ | 171 | /* We open code this to make sure cpu 0 is always considered */ |
| 170 | for (node = 0; node < MAX_NUMNODES; node = next_node(node, node_possible_map)) { | 172 | for (cpu = 0; cpu < nr_cpu_ids; cpu = cpumask_next(cpu, cpu_possible_mask)) { |
| 171 | struct flow_stats *stats = ovsl_dereference(flow->stats[node]); | 173 | struct flow_stats *stats = ovsl_dereference(flow->stats[cpu]); |
| 172 | 174 | ||
| 173 | if (stats) { | 175 | if (stats) { |
| 174 | spin_lock_bh(&stats->lock); | 176 | spin_lock_bh(&stats->lock); |
diff --git a/net/openvswitch/flow.h b/net/openvswitch/flow.h index 156a3029c17b..ae783f5c6695 100644 --- a/net/openvswitch/flow.h +++ b/net/openvswitch/flow.h | |||
| @@ -178,14 +178,14 @@ struct sw_flow { | |||
| 178 | struct hlist_node node[2]; | 178 | struct hlist_node node[2]; |
| 179 | u32 hash; | 179 | u32 hash; |
| 180 | } flow_table, ufid_table; | 180 | } flow_table, ufid_table; |
| 181 | int stats_last_writer; /* NUMA-node id of the last writer on | 181 | int stats_last_writer; /* CPU id of the last writer on |
| 182 | * 'stats[0]'. | 182 | * 'stats[0]'. |
| 183 | */ | 183 | */ |
| 184 | struct sw_flow_key key; | 184 | struct sw_flow_key key; |
| 185 | struct sw_flow_id id; | 185 | struct sw_flow_id id; |
| 186 | struct sw_flow_mask *mask; | 186 | struct sw_flow_mask *mask; |
| 187 | struct sw_flow_actions __rcu *sf_acts; | 187 | struct sw_flow_actions __rcu *sf_acts; |
| 188 | struct flow_stats __rcu *stats[]; /* One for each NUMA node. First one | 188 | struct flow_stats __rcu *stats[]; /* One for each CPU. First one |
| 189 | * is allocated at flow creation time, | 189 | * is allocated at flow creation time, |
| 190 | * the rest are allocated on demand | 190 | * the rest are allocated on demand |
| 191 | * while holding the 'stats[0].lock'. | 191 | * while holding the 'stats[0].lock'. |
diff --git a/net/openvswitch/flow_table.c b/net/openvswitch/flow_table.c index 957a3c31dbb0..ea7a8073fa02 100644 --- a/net/openvswitch/flow_table.c +++ b/net/openvswitch/flow_table.c | |||
| @@ -32,6 +32,7 @@ | |||
| 32 | #include <linux/module.h> | 32 | #include <linux/module.h> |
| 33 | #include <linux/in.h> | 33 | #include <linux/in.h> |
| 34 | #include <linux/rcupdate.h> | 34 | #include <linux/rcupdate.h> |
| 35 | #include <linux/cpumask.h> | ||
| 35 | #include <linux/if_arp.h> | 36 | #include <linux/if_arp.h> |
| 36 | #include <linux/ip.h> | 37 | #include <linux/ip.h> |
| 37 | #include <linux/ipv6.h> | 38 | #include <linux/ipv6.h> |
| @@ -79,17 +80,12 @@ struct sw_flow *ovs_flow_alloc(void) | |||
| 79 | { | 80 | { |
| 80 | struct sw_flow *flow; | 81 | struct sw_flow *flow; |
| 81 | struct flow_stats *stats; | 82 | struct flow_stats *stats; |
| 82 | int node; | ||
| 83 | 83 | ||
| 84 | flow = kmem_cache_alloc(flow_cache, GFP_KERNEL); | 84 | flow = kmem_cache_zalloc(flow_cache, GFP_KERNEL); |
| 85 | if (!flow) | 85 | if (!flow) |
| 86 | return ERR_PTR(-ENOMEM); | 86 | return ERR_PTR(-ENOMEM); |
| 87 | 87 | ||
| 88 | flow->sf_acts = NULL; | 88 | flow->stats_last_writer = -1; |
| 89 | flow->mask = NULL; | ||
| 90 | flow->id.unmasked_key = NULL; | ||
| 91 | flow->id.ufid_len = 0; | ||
| 92 | flow->stats_last_writer = NUMA_NO_NODE; | ||
| 93 | 89 | ||
| 94 | /* Initialize the default stat node. */ | 90 | /* Initialize the default stat node. */ |
| 95 | stats = kmem_cache_alloc_node(flow_stats_cache, | 91 | stats = kmem_cache_alloc_node(flow_stats_cache, |
| @@ -102,10 +98,6 @@ struct sw_flow *ovs_flow_alloc(void) | |||
| 102 | 98 | ||
| 103 | RCU_INIT_POINTER(flow->stats[0], stats); | 99 | RCU_INIT_POINTER(flow->stats[0], stats); |
| 104 | 100 | ||
| 105 | for_each_node(node) | ||
| 106 | if (node != 0) | ||
| 107 | |||
