mirror of
https://github.com/signalwire/freeswitch.git
synced 2026-10-04 02:03:59 +00:00
[Core] Avoid O(n^2) header dedup when cloning EF_UNIQ_HEADERS events (#3061)
* events: avoid O(n^2) header dedup when cloning EF_UNIQ_HEADERS events
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>
* tests: cover switch_event_dup uniqueness and add a dup benchmark
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>
* tests: add dup_faithful_copy regression for switch_event_dup
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>
* tests: gate dup_uniq_bench behind #ifdef BENCHMARK
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>
* switch_event: clarify EF_UNIQ_HEADERS dup comment, brace one-line test 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>
---------
Signed-off-by: Calvin Ellison <cellison@youmail.com>
Co-authored-by: Calvin Ellison <cellison@youmail.com>
This commit is contained in:
co-authored by
Calvin Ellison
parent
33f9f4db7c
commit
4fa2630a5b
@@ -1335,6 +1335,7 @@ SWITCH_DECLARE(void) switch_event_merge(switch_event_t *event, switch_event_t *t
|
||||
SWITCH_DECLARE(switch_status_t) switch_event_dup(switch_event_t **event, switch_event_t *todup)
|
||||
{
|
||||
switch_event_header_t *hp;
|
||||
int restore_uniq_headers = 0;
|
||||
|
||||
if (switch_event_create_subclass(event, SWITCH_EVENT_CLONE, todup->subclass_name) != SWITCH_STATUS_SUCCESS) {
|
||||
return SWITCH_STATUS_GENERR;
|
||||
@@ -1344,6 +1345,19 @@ SWITCH_DECLARE(switch_status_t) switch_event_dup(switch_event_t **event, switch_
|
||||
(*event)->event_user_data = todup->event_user_data;
|
||||
(*event)->bind_user_data = todup->bind_user_data;
|
||||
(*event)->flags = todup->flags;
|
||||
|
||||
/* The destination inherited todup's flags above. When EF_UNIQ_HEADERS is set,
|
||||
* switch_event_base_add_header() rescans the partial header list on every add
|
||||
* to keep names unique, making this copy loop O(n^2). A clone should reproduce
|
||||
* todup's list verbatim rather than run a fresh uniqueness pass, and a
|
||||
* well-formed EF_UNIQ_HEADERS source is already unique (its names were deduped
|
||||
* on insert), so that scan only adds cost here. Suppress it for the copy, then
|
||||
* restore the flag so later adds enforce uniqueness again. */
|
||||
if (switch_test_flag((*event), EF_UNIQ_HEADERS)) {
|
||||
switch_clear_flag((*event), EF_UNIQ_HEADERS);
|
||||
restore_uniq_headers = 1;
|
||||
}
|
||||
|
||||
for (hp = todup->headers; hp; hp = hp->next) {
|
||||
if (todup->subclass_name && !strcmp(hp->name, "Event-Subclass")) {
|
||||
continue;
|
||||
@@ -1359,6 +1373,10 @@ SWITCH_DECLARE(switch_status_t) switch_event_dup(switch_event_t **event, switch_
|
||||
}
|
||||
}
|
||||
|
||||
if (restore_uniq_headers) {
|
||||
switch_set_flag((*event), EF_UNIQ_HEADERS);
|
||||
}
|
||||
|
||||
if (todup->body) {
|
||||
(*event)->body = DUP(todup->body);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user