On Fri, Dec 27, 2013 at 8:03 PM, Ben Pfaff <b...@nicira.com> wrote: > ovsthread_counter is an abstract interface that could be implemented > different ways. The initial implementation is simple but less than > optimally efficient. > > Signed-off-by: Ben Pfaff <b...@nicira.com> > --- > lib/dpif-netdev.c | 26 ++++++++++++------ > lib/ovs-thread.c | 79 > +++++++++++++++++++++++++++++++++++++++++++++++++++++ > lib/ovs-thread.h | 19 +++++++++++++ > 3 files changed, 115 insertions(+), 9 deletions(-) > > diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c > index bf23958..bd01716 100644 > --- a/lib/dpif-netdev.c > +++ b/lib/dpif-netdev.c > @@ -102,9 +102,9 @@ struct dp_netdev { > struct seq *queue_seq; /* Incremented whenever a packet is queued. > */ > > /* Statistics. */ > - long long int n_hit; /* Number of flow table matches. */ > - long long int n_missed; /* Number of flow table misses. */ > - long long int n_lost; /* Number of misses not passed to client. */ > + struct ovsthread_counter *n_hit; /* Number of flow table matches. */ > + struct ovsthread_counter *n_missed; /* Number of flow table misses. */ > + struct ovsthread_counter *n_lost; /* Number of misses not passed up. */ > > /* Ports. */ > struct dp_netdev_port *ports[MAX_PORTS]; > @@ -294,6 +294,11 @@ create_dp_netdev(const char *name, const struct > dpif_class *class, > dp->queue_seq = seq_create(); > classifier_init(&dp->cls, NULL); > hmap_init(&dp->flow_table); > + > + dp->n_hit = ovsthread_counter_create(); > + dp->n_missed = ovsthread_counter_create(); > + dp->n_lost = ovsthread_counter_create(); > + > list_init(&dp->port_list); > dp->port_seq = seq_create(); > > @@ -358,6 +363,9 @@ dp_netdev_free(struct dp_netdev *dp) > LIST_FOR_EACH_SAFE (port, next, node, &dp->port_list) { > do_del_port(dp, port->port_no); > } > + ovsthread_counter_destroy(dp->n_hit); > + ovsthread_counter_destroy(dp->n_missed); > + ovsthread_counter_destroy(dp->n_lost); > dp_netdev_purge_queues(dp); > seq_destroy(dp->queue_seq); > classifier_destroy(&dp->cls); > @@ -403,9 +411,9 @@ dpif_netdev_get_stats(const struct dpif *dpif, struct > dpif_dp_stats *stats) > > ovs_mutex_lock(&dp_netdev_mutex); > stats->n_flows = hmap_count(&dp->flow_table); > - stats->n_hit = dp->n_hit; > - stats->n_missed = dp->n_missed; > - stats->n_lost = dp->n_lost; > + stats->n_hit = ovsthread_counter_read(dp->n_hit); > + stats->n_missed = ovsthread_counter_read(dp->n_missed); > + stats->n_lost = ovsthread_counter_read(dp->n_lost); > stats->n_masks = UINT32_MAX; > stats->n_mask_hit = UINT64_MAX; > ovs_mutex_unlock(&dp_netdev_mutex); > @@ -1270,9 +1278,9 @@ dp_netdev_port_input(struct dp_netdev *dp, struct > dp_netdev_port *port, > dp_netdev_execute_actions(dp, &key, packet, > netdev_flow->actions, > netdev_flow->actions_len); > - dp->n_hit++; > + ovsthread_counter_inc(dp->n_hit, 1); > } else { > - dp->n_missed++; > + ovsthread_counter_inc(dp->n_missed, 1); > dp_netdev_output_userspace(dp, packet, DPIF_UC_MISS, &key, NULL); > } > } > @@ -1380,7 +1388,7 @@ dp_netdev_output_userspace(struct dp_netdev *dp, struct > ofpbuf *packet, > > return 0; > } else { > - dp->n_lost++; > + ovsthread_counter_inc(dp->n_lost, 1); > return ENOBUFS; > } > } > diff --git a/lib/ovs-thread.c b/lib/ovs-thread.c > index aa3ad15..d35accb 100644 > --- a/lib/ovs-thread.c > +++ b/lib/ovs-thread.c > @@ -21,6 +21,7 @@ > #include <stdlib.h> > #include <unistd.h> > #include "compiler.h" > +#include "hash.h" > #include "poll-loop.h" > #include "socket-util.h" > #include "util.h" > @@ -310,6 +311,84 @@ may_fork(void) > return !must_not_fork; > } > > +/* ovsthread_counter. > + * > + * We implement the counter as an array of N_COUNTERS individual counters, > each > + * with its own lock. Each thread uses one of the counters chosen based on a > + * hash of the thread's ID, the idea being that, statistically, different > + * threads will tend to use different counters and therefore avoid > + * interfering with each other. > + * > + * Undoubtedly, better implementations are possible. */ > + > +/* Basic counter structure. */ > +struct ovsthread_counter__ { > + struct ovs_mutex mutex; > + unsigned long long int value; > +}; > + > +/* Pad the basic counter structure to 64 bytes to avoid cache line > + * interference. */ > +struct ovsthread_counter { > + struct ovsthread_counter__ c; > + char pad[ROUND_UP(sizeof(struct ovsthread_counter__), 64) > + - sizeof(struct ovsthread_counter__)]; > +}; > + > +#define N_COUNTERS 16 > + > +struct ovsthread_counter * > +ovsthread_counter_create(void) > +{ > + struct ovsthread_counter *c; > + int i; > + > + c = xmalloc(N_COUNTERS * sizeof *c); > + for (i = 0; i < N_COUNTERS; i++) { > + ovs_mutex_init(&c[i].c.mutex); > + c[i].c.value = 0; > + } > + return c; > +} > + > +void > +ovsthread_counter_destroy(struct ovsthread_counter *c) > +{ > + if (c) { > + int i; > + > + for (i = 0; i < N_COUNTERS; i++) { > + ovs_mutex_destroy(&c[i].c.mutex); > + } > + free(c); > + } > +} > + > +void > +ovsthread_counter_inc(struct ovsthread_counter *c, unsigned long long int n) > +{ > + c = &c[hash_int(ovsthread_id_self(), 0) % N_COUNTERS]; > + Does it make sense optimize this locking so that threads running on same numa-node likely share lock? we can use process id hashing to achieve it easily.
> + ovs_mutex_lock(&c->c.mutex); > + c->c.value += n; > + ovs_mutex_unlock(&c->c.mutex); > +} > + > +unsigned long long int > +ovsthread_counter_read(const struct ovsthread_counter *c) > +{ > + unsigned long long int sum; > + int i; > + > + sum = 0; > + for (i = 0; i < N_COUNTERS; i++) { > + ovs_mutex_lock(&c[i].c.mutex); > + sum += c[i].c.value; > + ovs_mutex_unlock(&c[i].c.mutex); > + } > + return sum; > +} > + > /* Parses /proc/cpuinfo for the total number of physical cores on this system > * across all CPU packages, not counting hyper-threads. > * > diff --git a/lib/ovs-thread.h b/lib/ovs-thread.h > index b6d973f..a3e2696 100644 > --- a/lib/ovs-thread.h > +++ b/lib/ovs-thread.h > @@ -494,6 +494,25 @@ ovsthread_id_self(void) > return *ovsthread_id_get(); > } > > +/* Simulated global counter. > + * > + * Incrementing such a counter is meant to be cheaper than incrementing a > + * global counter protected by a lock. It is probably more expensive than > + * incrementing a truly thread-local variable, but such a variable has no > + * straightforward way to get the sum. > + * > + * > + * Thread-safety > + * ============= > + * > + * Fully thread-safe. */ > + > +struct ovsthread_counter *ovsthread_counter_create(void); > +void ovsthread_counter_destroy(struct ovsthread_counter *); > +void ovsthread_counter_inc(struct ovsthread_counter *, unsigned long long > int); > +unsigned long long int ovsthread_counter_read( > + const struct ovsthread_counter *); > + > void assert_single_threaded_at(const char *where); > #define assert_single_threaded() assert_single_threaded_at(SOURCE_LOCATOR) > > -- > 1.7.10.4 > > _______________________________________________ > dev mailing list > dev@openvswitch.org > http://openvswitch.org/mailman/listinfo/dev _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev