java-topology/docs/tickets/freeswitch-0001-conference-relationship-scan-per-sample.md

2.6 KiB
Raw Permalink Blame History

freeswitch-0001 — mod_conference.c: relationship list scan inside per-sample audio mixing (O(M²·R·S))

Severity: CRITICAL File: src/mod/applications/mod_conference/mod_conference.c Lines: 617-673 (audio mixing loop)

Pattern

// Outer: for each output member
for (omember = conference->members; omember; omember = omember->next) {
    // Middle: for each audio sample
    for (x = 0; x < bytes / 2; x++) {
        ...
        if (conference->relationship_total) {
            // Inner: for each input member
            for (imember = conference->members; imember; imember = imember->next) {
                // Innermost: linear scan of singly-linked relationship list
                for (rel = imember->relationships; rel; rel = rel->next) {
                    if (rel->id == omember->id || rel->id == 0) { ... break; }
                }
                if (!found) {
                    for (rel = omember->relationships; rel; rel = rel->next) {
                        if (rel->id == imember->id || rel->id == 0) { ... break; }
                    }
                }
            }
        }
    }
}

conference_relationship_t is a singly-linked list (mod_conference.h:770-774). The innermost relationship scan is O(R) per (omember, imember, sample) triple.

Complexity

O(S × M × M × R) per mix cycle where:

  • S = samples per frame (typically 160 at 8kHz/20ms)
  • M = member count
  • R = relationships per member

At M=20 members, S=160, R=5 relationships: 20×20×160×5 = 3,200,000 ops per mix cycle (50Hz). = 160M ops/sec just for relationship scanning, all inside a mutex-held loop.

Root Cause

The relationship check is nested inside the per-sample loop. The relationship result (can A hear B?) does not change per-sample — it only changes on relationship updates.

Fix

Pre-compute relationship matrix outside the sample loop:

Before for (x = 0; x < bytes/2; x++), build a boolean matrix or set:

// Per mixing frame (outside sample loop):
// For each (omember, imember) pair, check relationships once → store in a bitmask
// Then the per-sample loop just does: if (exclude_matrix[omember_idx][imember_idx]) z -= rptr[x];

Alternative: use switch_core_hash keyed by (omember->id << 32) | imember->id for O(1) lookup, pre-computed at frame start, invalidated only on conference_member_add_relationship().

Speedup (estimated)

Relationship check moves from O(R) inside O(S×M²) to O(1) table lookup: ~500× at M=20, R=5. Full fix moves relationship resolution entirely outside the sample loop: O(S×M²) → O(M²+S×M).