[Core] Avoid O(n^2) header dedup when cloning EF_UNIQ_HEADERS events#3061
Open
celliso1 wants to merge 5 commits into
Open
[Core] Avoid O(n^2) header dedup when cloning EF_UNIQ_HEADERS events#3061celliso1 wants to merge 5 commits into
celliso1 wants to merge 5 commits into
Conversation
Author
|
@andywolk — would you have a moment to look at this? It's a self-contained ~10-line hot-path fix (O(n^2) -> O(n) in Since opening it I validated the change on a production voicemail fleet — same-node before/after under real deposit load — and the win holds across CPU, hypervisor, and FreeSWITCH version:
A consistent ~85% reduction in the per-add uniqueness scan (also summarized in the PR body). Happy to adjust anything — thanks! |
celliso1
force-pushed
the
events-avoid-on2-header-dedup-on-clone
branch
from
June 28, 2026 16:52
d2e9057 to
5a8505d
Compare
morbit85
requested changes
Jun 29, 2026
andywolk
requested changes
Jun 29, 2026
andywolk
requested changes
Jun 29, 2026
morbit85
approved these changes
Jun 29, 2026
andywolk
approved these changes
Jun 29, 2026
andywolk
force-pushed
the
events-avoid-on2-header-dedup-on-clone
branch
from
July 1, 2026 13:31
7cac871 to
1002510
Compare
13 tasks
andywolk
force-pushed
the
events-avoid-on2-header-dedup-on-clone
branch
from
July 4, 2026 13:37
1002510 to
0f04cf5
Compare
switch_event_dup() copies a source event header by header via
switch_event_add_header_string(). When the source carries
EF_UNIQ_HEADERS (SWITCH_EVENT_CHANNEL_DATA / REQUEST_PARAMS / MESSAGE --
e.g. a channel's variable list), the destination inherits the flag, so
switch_event_base_add_header() runs a full switch_event_del_header()
linear scan of the partially-built list on every add to enforce
uniqueness. Cloning an N-header event is therefore O(n^2).
That scan is redundant while cloning: the source already enforced
uniqueness on insert, so copying it cannot introduce duplicates
regardless of the per-add scan. Clear EF_UNIQ_HEADERS on the
destination for the duration of the copy loop, then restore it.
This is a hot path. switch_channel_execute_on() -- invoked on ring,
answer, media, transfer, park, record and playback -- calls
switch_core_get_variables() and switch_channel_get_variables(), each of
which dup()s an EF_UNIQ_HEADERS event. On a production voicemail server
carrying ~191 channel variables per call, switch_event_del_header_val()
accounted for 47-69% of all FreeSWITCH CPU time in on-box perf profiles.
Microbenchmark (tests/unit/switch_event.c, switch_event_dup of an
N-header CHANNEL_DATA event, 4000 iterations, time per dup):
N before after
50 4.80 us 3.19 us
100 11.34 us 5.88 us
191 28.29 us 10.99 us (2.6x)
400 107.05 us 23.27 us (4.6x)
"before" scales ~O(n^2); "after" is linear. Behaviour is unchanged: the
clone has identical headers, values and order, keeps EF_UNIQ_HEADERS,
and still enforces uniqueness on subsequent adds (covered by the test).
Signed-off-by: Calvin Ellison <cellison@youmail.com>
Adds a dup_uniq_bench case to the switch_event unit test. For an EF_UNIQ_HEADERS (CHANNEL_DATA) source event it verifies the clone preserves header count, values and the EF_UNIQ_HEADERS flag, and that re-setting an existing key does not produce a duplicate in the clone. It also prints a switch_event_dup timing sweep across header counts, used to validate the O(n^2) -> O(n) change in switch_event_dup(). Signed-off-by: Calvin Ellison <cellison@youmail.com>
Covers the EF_UNIQ_HEADERS edge case for the O(n^2)->O(n) dup change: a well-formed EF_UNIQ source stays unique through dup, and a malformed source already holding duplicate names (only possible if headers were added before the flag was set) is copied faithfully rather than silently collapsed to the last value. Signed-off-by: Calvin Ellison <cellison@youmail.com>
The dup_uniq_bench timing sweep is a benchmark rather than a pass/fail correctness test. Guard the whole test with #ifdef BENCHMARK (matching the existing benchmark in this file) so it stays out of normal test runs; dup_faithful_copy continues to cover correctness of the switch_event_dup change. Signed-off-by: Calvin Ellison <cellison@youmail.com>
…t loops Address review feedback on the switch_event_dup change: - src/switch_event.c: the fast-path comment claimed todup's headers are "already unique" unconditionally. That only holds when EF_UNIQ_HEADERS is set (names are deduped on insert); reword to scope the claim to that case and to the verbatim-copy intent. - tests/unit/switch_event.c: expand one-line loops to braced form per the SignalWire coding guidelines. Signed-off-by: Calvin Ellison <cellison@youmail.com>
andywolk
force-pushed
the
events-avoid-on2-header-dedup-on-clone
branch
from
July 10, 2026 18:56
0f04cf5 to
71b7038
Compare
13 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Problem
switch_event_dup()clones a source event by re-adding every header withswitch_event_add_header_string(). When the source hasEF_UNIQ_HEADERSset — which is the case for
SWITCH_EVENT_CHANNEL_DATA,SWITCH_EVENT_REQUEST_PARAMSandSWITCH_EVENT_MESSAGE, i.e. the eventsused to hold a channel's variable list — the destination inherits the
flag, so
switch_event_base_add_header()callsswitch_event_del_header()(a full linear scan of the list built so far) on every add to enforce
uniqueness. Duplicating an N-header event is therefore O(n²).
The scan is wasted work: the source list is already unique (uniqueness was
enforced when each header was first inserted), so copying it can never
create a duplicate, with or without the per-add scan.
Why it matters
switch_event_dup()backsswitch_core_get_variables()andswitch_channel_get_variables(), which clone anEF_UNIQ_HEADERSvariable event. Any workload that duplicates large
EF_UNIQ_HEADERSevents — channel-variable snapshots, or ESL consumers cloning big
events — pays this O(n²) on every dup.
On a production voicemail box carrying ~191 channel variables per call,
switch_event_del_header_val()(the per-add uniqueness scan) is 47–69%of FS CPU in
perf, and call-graph profiles show a substantial sharereached through
execute_on -> *_get_variables -> switch_event_dup— thepath this patch fixes.
Scope: this patch fixes the dup path only.
del_header_valis alsoreached via
switch_event_merge()(also inexecute_on) and per-variableswitch_channel_set_variable(); those are separate O(n²) paths thischange does not address (future work: avoid the dup+merge in
execute_on,or hash-index event headers). The exact prod split between these callers
is not yet quantified.
Fix
Clear
EF_UNIQ_HEADERSon the destination for the duration of the copyloop in
switch_event_dup(), then restore it. ~10 lines, no API change.Microbench
tests/unit/switch_event.c(dup_uniq_bench), dup of an N-headerCHANNEL_DATAevent, 4000 iterations, time per dup:Real-call validation
Validated on production voicemail hosts under real deposit load — across
CPU, hypervisor, and FreeSWITCH version. Same-node, before vs after
swapping in the patched lib (40 s
perfwindows, self-time):del_header_valstockA consistent ~85% reduction in the per-add uniqueness scan across all
three environments — the cache-bound Intel box (highest stock cost) drops
the most in absolute terms, −48 points of FS CPU. The call-graph
confirms the mechanism: with the patch the
switch_channel_execute_on → *_get_variables → switch_event_dupchains nolonger reach
del_header_val; the ~6–8% residual is a separate path(
switch_event_merge, also reached fromexecute_on) that this changedeliberately does not touch.
Type of Change
Testing
Profiled on production voicemail deposit service.
Checklist