Commit Graph

2 Commits

Author SHA1 Message Date
Uladzimir Karpenka 12dc701604 analyzer: block the dispatcher on a semaphore, stop spawning a thread per frame
Two pieces of pure overhead, neither of them doing any DSP.

The POSIX QueueUserWorkItem() shim spawned a thread and immediately joined
it. That is a synchronous call -- the parallelism the Windows thread pool
provides is absent here either way -- so it only bought a pthread_create()
and its stack mmap, ~15 us, for every FFT frame. Call the function directly.
(The Windows build is untouched and still gets its thread pool; a real pool
for POSIX would be a separate change, and only pays off for num_stitch > 1.)

The dispatcher thread ran `for (each ss, LO) {...} Sleep(1);`, and Sleep(1)
is usleep(1000), so it woke 1000 times a second to re-read the same flags.
That burned 0.63% of a core even with no samples arriving. Give it a
semaphore instead, signalled by the four Spectrum*() entry points when new
samples land, and by SetAnalyzer/DestroyAnalyzer after they raise
end_dispatcher so the blocking wait always has a way out. SetAnalyzer holds
SetAnalyzerSection while it waits for the dispatcher to quit, and the
dispatcher never takes that section, so signalling from under it is safe.

Measured on an Apple M1 Pro; 16384-point complex FFT, 1024-sample buffers,
~40 pixel frames/s, CPU of all threads via getrusage:

    idle, no samples at all      0.63% of a core  ->  0.00%
    under load                   3.54%            ->  2.65%

Output pixels are bit-identical. Stressed with 3 create/destroy cycles and
24 on-the-fly SetAnalyzer reconfigurations while a second thread fed samples:
no deadlock.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:59:20 +03:00
Uladzimir Karpenka 89c8a0e2b5 first commit 2026-06-01 15:58:45 +03:00