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 <amu...@redhat.com>
> ---
>  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 <dce...@redhat.com>

Thanks,
Dumitru


_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to