On Tue, Sep 16, 2025 at 1:55 PM Dumitru Ceara <[email protected]> wrote:
> Hi Ales,
>
> Thanks for the patch!
>
> On 9/11/25 1:44 PM, Ales Musil wrote:
> > The function has only single call site and was used for modifying
> > flows where the only actions in the list were conjunctions. By
> > specializing we can make durther assumptions that allows us to
>
> Typo: durther
>
> > optimize as much as possible. The main issue with the function was
> > very high number of allocations, 90% of them were actually to perform
> > a duplication check when we try to append new conjnctions into
>
> Typo: conjnctions
>
> > already existing flow.
> >
> > The strategy to avoid those allocations is to allocate the array
> > of references only once instead of going one by name effectively
>
> "one by name"? Did you mean to say "one by one"?
>
> > turning the allocation complexity of that function fron O(N), where
>
> Typo: fron
>
> > N is the number of already existing conjunctions into O(1) where we
> > allocate constant number of times depending on the code path.
> >
> > This also improves the performance of operations directly related
> > to conjunctions, mainly port group and address set modifications
> > within ACLs.
> >
> > Below are numbers from a test run with 2000 LSPs, 1000 PGs, 1000 ACLs
> > that resulted with flows that had 1000 conjunctions as an action.
> >
> > Without the change:
> > ovn-controller(s) completion: 910ms
> > node: lflow_output, handler for input port_groups took 427ms
> >
> > With the change:
> > ovn-controller(s) completion: 407ms
> > node: lflow_output, handler for input port_groups took 157ms
> >
> > So the speed up for the sync call is on avarage aroung 2.2x faster> and
> the speedup of precessing those lflows due to port group change
> > is on avarage 2.7x faster. This is also not limited only to port
>
> Typos: avarage (x2), aroung, precessing
>
> > groups, but the address set changes resulting in conjunctions will
> > benefit too.
> >
> > Fixes: 5ad4e5395b9e ("ofctrl: Prevent conjunction duplication")
> > Fixes: e659bab31a91 ("Combine conjunctions with identical matches into
> one flow.")
> > Signed-off-by: Ales Musil <[email protected]>
> > ---
> > controller/lflow.c | 23 ++------
> > controller/ofctrl.c | 136 +++++++++++++++++++++++++-------------------
> > controller/ofctrl.h | 15 ++---
> > 3 files changed, 92 insertions(+), 82 deletions(-)
> >
> > diff --git a/controller/lflow.c b/controller/lflow.c
> > index b75ae5c0d..11ec33faf 100644
> > --- a/controller/lflow.c
> > +++ b/controller/lflow.c
> > @@ -938,24 +938,11 @@ add_matches_to_flow_table(const struct
> sbrec_logical_flow *lflow,
> > if (vector_len(&m->conjunctions) > 1) {
> > ovs_assert(!as_info.name);
> > }
> > - uint64_t conj_stubs[64 / 8];
> > - struct ofpbuf conj;
> > -
> > - ofpbuf_use_stub(&conj, conj_stubs, sizeof conj_stubs);
> > - const struct cls_conjunction *src;
> > - VECTOR_FOR_EACH_PTR (&m->conjunctions, src) {
> > - struct ofpact_conjunction *dst =
> ofpact_put_CONJUNCTION(&conj);
> > - dst->id = src->id;
> > - dst->clause = src->clause;
> > - dst->n_clauses = src->n_clauses;
> > - }
> > -
> > - ofctrl_add_or_append_flow(l_ctx_out->flow_table, ptable,
> > - lflow->priority, 0,
> > - &m->match, &conj,
> &lflow->header_.uuid,
> > - ctrl_meter_id,
> > - as_info.name ? &as_info : NULL);
> > - ofpbuf_uninit(&conj);
> > + ofctrl_add_or_append_conj_flow(l_ctx_out->flow_table,
> ptable,
> > + lflow->priority, &m->match,
> > + &m->conjunctions,
> > + &lflow->header_.uuid,
> ctrl_meter_id,
> > + as_info.name ? &as_info :
> NULL);
> > }
> > }
> >
> > diff --git a/controller/ofctrl.c b/controller/ofctrl.c
> > index 2a518161f..20d4bed01 100644
> > --- a/controller/ofctrl.c
> > +++ b/controller/ofctrl.c
> > @@ -290,6 +290,11 @@ static void remove_flows_from_sb_to_flow(struct
> ovn_desired_flow_table *,
> > struct sb_to_flow *,
> > const char *log_msg,
> > struct uuidset
> *flood_remove_nodes);
> > +static void ovn_flow_uninit(struct ovn_flow *f);
> > +static void ovn_flow_init(struct ovn_flow *f, uint8_t table_id,
> > + uint16_t priority, uint64_t cookie,
> > + const struct match *match, void *actions,
> > + size_t action_len, uint32_t meter_id);
> >
> > /* OpenFlow connection to the switch. */
> > static struct rconn *swconn;
> > @@ -1300,77 +1305,91 @@ ofpact_ref_find(const struct hmap *refs, const
> struct ofpact *ofpact)
> > return NULL;
> > }
> >
> > -static void
> > -ofpact_refs_destroy(struct hmap *refs)
> > +static inline void
> > +ofpacts_append(struct ovn_flow *flow, const struct ofpbuf *append)
> > {
> > - struct ofpact_ref *ref;
> > - HMAP_FOR_EACH_POP (ref, hmap_node, refs) {
> > - free(ref);
> > + size_t new_len = flow->ofpacts_len + append->size;
> > +
> > + flow->ofpacts = xrealloc(flow->ofpacts, new_len);
> > + memcpy((uint8_t *) flow->ofpacts + flow->ofpacts_len,
> > + append->data, append->size);
> > + flow->ofpacts_len = new_len;
> > +}
> > +
> > +static inline struct ofpbuf *
> > +create_conjuction_actions(const struct vector *conjunctions,
> > + const struct hmap *existing)
> > +{
> > + size_t len = vector_len(conjunctions);
> > + struct ofpbuf *ofpbuf =
> > + ofpbuf_new(sizeof(struct ofpact_conjunction) * len);
> > +
> > + const struct cls_conjunction *cls;
> > + VECTOR_FOR_EACH_PTR (conjunctions, cls) {
> > + struct ofpact_conjunction *conj =
> ofpact_put_CONJUNCTION(ofpbuf);
> > + conj->id = cls->id;
> > + conj->clause = cls->clause;
> > + conj->n_clauses = cls->n_clauses;
> > +
> > + if (existing && ofpact_ref_find(existing, &conj->ofpact)) {
> > + ofpbuf_pull(ofpbuf, sizeof *conj);
> > + }
> > }
> > - hmap_destroy(refs);
> > +
> > + return ofpbuf;
> > }
> >
> > /* Either add a new flow, or append actions on an existing flow. If the
> > * flow existed, a new link will also be created between the new sb_uuid
> > * and the existing flow. */
> > void
> > -ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows,
> > - uint8_t table_id, uint16_t priority, uint64_t
> cookie,
> > - const struct match *match,
> > - const struct ofpbuf *actions,
> > - const struct uuid *sb_uuid,
> > - uint32_t meter_id,
> > - const struct addrset_info *as_info)
> > +ofctrl_add_or_append_conj_flow(struct ovn_desired_flow_table
> *desired_flows,
> > + uint8_t table_id, uint16_t priority,
> > + const struct match *match,
> > + const struct vector *conjunctions,
> > + const struct uuid *sb_uuid,
> > + uint32_t meter_id,
> > + const struct addrset_info *as_info)
> > {
> > struct desired_flow *existing;
> > struct desired_flow *f;
> > -
> > - f = desired_flow_alloc(table_id, priority, cookie, match, actions,
> > - meter_id);
> > - existing = desired_flow_lookup_conjunctive(desired_flows, &f->flow);
> > + struct ofpbuf *actions;
> > +
> > + /* Create flow just to search for existing one, this avoids the
> allocation
> > + * of ofpacts buffer if it's not needed yet. */
> > + struct ovn_flow search_flow;
> > + ovn_flow_init(&search_flow, table_id, priority, 0,
> > + match, NULL, 0, meter_id);
> > + existing = desired_flow_lookup_conjunctive(desired_flows,
> &search_flow);
> > if (existing) {
> > struct hmap existing_conj = HMAP_INITIALIZER(&existing_conj);
> > -
> > + /* We can calculate the length directly because the flow actions
> > + * consist only of conjunctions. Make a rough assert to ensure
> > + * that is that case in the future. */
> > + size_t conj_size = sizeof (struct ofpact_conjunction);
>
> Nit: I think it should be "sizeof(...)".
>
> > + ovs_assert(!(existing->flow.ofpacts_len % conj_size));
> > + struct ofpact_ref *refs =
> > + xmalloc(sizeof *refs * existing->flow.ofpacts_len /
> conj_size);
> > +
> > + size_t index = 0;
> > struct ofpact *ofpact;
> > OFPACT_FOR_EACH (ofpact, existing->flow.ofpacts,
> > existing->flow.ofpacts_len) {
> > - if (ofpact->type != OFPACT_CONJUNCTION) {
> > - continue;
> > - }
>
> Just to be extra sure we don't miss a case could we also add an
> assertion here? E.g.:
>
> ovs_assert(ofpact->type == OFPACT_CONJUNCTION);
>
> Or a VLOG_WARN_RL(), I'm on the fence whether we should crash or not if
> we determine there's a bug here.
>
> I see that all flows here must be "pure" conjunctive flows but in theory
> flow_action_has_conj() could return true also if the flow has a mix of
> conj and non-conjunction actions.
>
> > -
> > - struct ofpact_ref *ref = xmalloc(sizeof *ref);
> > + struct ofpact_ref *ref = &refs[index++];
> > ref->ofpact = ofpact;
> > uint32_t hash = hash_bytes(ofpact, ofpact->len, 0);
> > hmap_insert(&existing_conj, &ref->hmap_node, hash);
> > }
> >
> > - /* There's already a flow with this particular match and action
> > - * 'conjunction'. Append the action to that flow rather than
> > - * adding a new flow.
> > - */
> > - uint64_t compound_stub[64 / 8];
> > - struct ofpbuf compound;
> > - ofpbuf_use_stub(&compound, compound_stub,
> sizeof(compound_stub));
> > - ofpbuf_put(&compound, existing->flow.ofpacts,
> > - existing->flow.ofpacts_len);
> > -
> > - OFPACT_FOR_EACH (ofpact, f->flow.ofpacts, f->flow.ofpacts_len) {
> > - if (ofpact->type != OFPACT_CONJUNCTION ||
> > - !ofpact_ref_find(&existing_conj, ofpact)) {
> > - ofpbuf_put(&compound, ofpact,
> OFPACT_ALIGN(ofpact->len));
> > - }
> > - }
> > -
> > - ofpact_refs_destroy(&existing_conj);
> > -
> > + actions = create_conjuction_actions(conjunctions,
> &existing_conj);
> > mem_stats.desired_flow_usage -= desired_flow_size(existing);
> > - free(existing->flow.ofpacts);
> > - existing->flow.ofpacts = xmemdup(compound.data, compound.size);
> > - existing->flow.ofpacts_len = compound.size;
> > + ofpacts_append(&existing->flow, actions);
> > mem_stats.desired_flow_usage += desired_flow_size(existing);
> >
> > - ofpbuf_uninit(&compound);
> > - desired_flow_destroy(f);
> > +
> > + free(refs);
> > + hmap_destroy(&existing_conj);
> > +
> > f = existing;
> >
> > /* Since the flow now shared by more than one SB lflows, don't
> track
> > @@ -1388,17 +1407,20 @@ ofctrl_add_or_append_flow(struct
> ovn_desired_flow_table *desired_flows,
> > }
> > link_flow_to_sb(desired_flows, f, sb_uuid, NULL);
> > } else {
> > + actions = create_conjuction_actions(conjunctions, NULL);
> > + f = desired_flow_alloc(table_id, priority, 0, match, actions,
> > + meter_id);
> > hmap_insert(&desired_flows->match_flow_table,
> &f->match_hmap_node,
> > f->flow.hash);
> > link_flow_to_sb(desired_flows, f, sb_uuid, as_info);
> > }
> > track_flow_add_or_modify(desired_flows, f);
> >
> > - if (existing) {
> > - ovn_flow_log(&f->flow, "ofctrl_add_or_append_flow (append)");
> > - } else {
> > - ovn_flow_log(&f->flow, "ofctrl_add_or_append_flow (add)");
> > - }
> > + ovn_flow_log(&f->flow, existing
> > + ? "ofctrl_add_or_append_conj_flow (append)"
> > + : "ofctrl_add_or_append_conj_flow (add)");
> > + ovn_flow_uninit(&search_flow);
> > + ofpbuf_delete(actions);
> > }
> >
> > void
> > @@ -1621,13 +1643,13 @@ remove_flows_from_sb_to_flow(struct
> ovn_desired_flow_table *flow_table,
> > static void
> > ovn_flow_init(struct ovn_flow *f, uint8_t table_id, uint16_t priority,
> > uint64_t cookie, const struct match *match,
> > - const struct ofpbuf *actions, uint32_t meter_id)
> > + void *actions, size_t action_len, uint32_t meter_id)
> > {
> > f->table_id = table_id;
> > f->priority = priority;
> > minimatch_init(&f->match, match);
> > - f->ofpacts = xmemdup(actions->data, actions->size);
> > - f->ofpacts_len = actions->size;
> > + f->ofpacts = actions ? xmemdup(actions, action_len) : NULL;
> > + f->ofpacts_len = action_len;
> > f->hash = ovn_flow_match_hash(f);
> > f->cookie = cookie;
> > f->ctrl_meter_id = meter_id;
> > @@ -1651,8 +1673,8 @@ desired_flow_alloc(uint8_t table_id, uint16_t
> priority, uint64_t cookie,
> > ovs_list_init(&f->track_list_node);
> > f->installed_flow = NULL;
> > f->is_deleted = false;
> > - ovn_flow_init(&f->flow, table_id, priority, cookie, match, actions,
> > - meter_id);
> > + ovn_flow_init(&f->flow, table_id, priority, cookie, match,
> actions->data,
> > + actions->size, meter_id);
> >
> > mem_stats.desired_flow_usage += desired_flow_size(f);
> > return f;
> > diff --git a/controller/ofctrl.h b/controller/ofctrl.h
> > index 76e2fbece..abd2ff1c9 100644
> > --- a/controller/ofctrl.h
> > +++ b/controller/ofctrl.h
> > @@ -34,6 +34,7 @@ struct sbrec_meter_table;
> > struct sbrec_ecmp_nexthop_table;
> > struct shash;
> > struct tracked_acl_ids;
> > +struct vector;
> >
> > struct ovn_desired_flow_table {
> > /* Hash map flow table using flow match conditions as hash key.*/
> > @@ -96,13 +97,13 @@ void ofctrl_add_flow(struct ovn_desired_flow_table
> *, uint8_t table_id,
> > const struct match *, const struct ofpbuf *ofpacts,
> > const struct uuid *);
> >
> > -void ofctrl_add_or_append_flow(struct ovn_desired_flow_table *,
> > - uint8_t table_id, uint16_t priority,
> > - uint64_t cookie, const struct match *,
> > - const struct ofpbuf *actions,
> > - const struct uuid *sb_uuid,
> > - uint32_t meter_id,
> > - const struct addrset_info *);
> > +void ofctrl_add_or_append_conj_flow(struct ovn_desired_flow_table *,
> > + uint8_t table_id, uint16_t priority,
> > + const struct match *,
> > + const struct vector *conjunctions,
> > + const struct uuid *sb_uuid,
> > + uint32_t meter_id,
> > + const struct addrset_info *);
> >
> > void ofctrl_add_flow_metered(struct ovn_desired_flow_table
> *desired_flows,
> > uint8_t table_id, uint16_t priority,
>
> The rest looks good to me. With the small typos/nits addressed and
> optionally with the assert/log added:
>
> Acked-by: Dumitru Ceara <[email protected]>
>
> Thanks,
> Dumitru
>
>
Thank you Mark and Dumitru,
I have addressed all nits, added an assert and merged this into main.
Regards,
Ales
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev