Files
Uladzimir KarpenkaandClaude Opus 4.8 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

219 lines
10 KiB
C

/* analyzer.h
This file is part of a program that implements a Spectrum Analyzer
used in conjunction with software-defined-radio hardware.
Copyright (C) 2012, 2013, 2014, 2016, 2023, 2025 Warren Pratt, NR0V
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _analyzer_h
#define _analyzer_h
#include "comm.h"
typedef struct _dp
{
int max_size; // maximum fft size to be used
int max_num_fft; // maximum number of LO positions per sub-span to be used
int max_stitch; // maximum number of sub-spans to be concatenated
// NOTE: max_size, max_num_fft, and max_stitch MUST BE <= THE
// CORRESPONDING VALUES IN <analyzer.h>!!
int num_fft; // current number of ffts in use
int num_pixout; // current number of detector/averages/pixel value outputs
int size; // current size of fft input sample vector
int out_size; // current size of fft output vector
int window_type; // type of the window function to be applied
int overlap; // number of samples re-used per fft, range 0 to size-1
int flip[dMAX_NUM_FFT]; // 0 for low-side LO => do NOT flip; 1 for high-side LO => FLIP
int clip; // number of bins to clip off on EACH end of the sub-span fft
// ASSUMES size/2 IS AN EVEN NUMBER!!!
double fsclipL; // number of intervals to clip off the lower end of the TOTAL SPAN
double fsclipH; // number of intervals to clip off the upper end of the TOTAL SPAN
int fscL; // fsclipL modulo (out_size - 2 * clip)
int fscH; // fsclipH modulo (out_size - 2 * clip)
int begin_ss; // number of first sub-span that is NOT completely clipped off
int end_ss; // number of last sub-span that is NOT completely clipped off
int ss_bins[dMAX_STITCH]; // number of bins delivered by eliminate()/Celiminate in each sub-span
volatile LONG input_busy[dMAX_STITCH][dMAX_NUM_FFT];
int num_pixels; // number of pixels requested
int num_stitch; // number of results to be stitched together to generate the pixel frame
unsigned long long stitch_flag;
int spec_flag[dMAX_STITCH]; // flags showing if all ffts for a sub-span are done so elimination can proceed
double pix_per_bin; // number of pixels per fft bin, note that this is fractional, not integral
double det_offset; // offset needed in detector
double bin_per_pix; // number of fft bins per pixel, this is fractional and != 1.0/pix_per_bin
double scale; // output amplitude scale factor
double PiAlpha; // parameter for Kaiser window function
int cal_set; // specifies which set of calibration data to use
double f_min; // frequency at first pixel (for calibration)
double f_max; // frequency at last pixel (for calibration)
int cal_changed; // flag to indicate that the calibration data has changed
double *window; // pointer to buffer to hold window coefficients
double *result[dMAX_STITCH]; // pointers to buffer to hold elimination results for each sub-span
dOUTREAL *pixels[dMAX_PIXOUTS][dNUM_PIXEL_BUFFS]; // pointers pixel output buffers
double *t_pixels[dMAX_PIXOUTS]; // pointer to temporary pixel buffer //pointer to temporary pixel buffer for non-averaged data
int w_pix_buff[dMAX_PIXOUTS]; // number of pixel buffer owned by writing process
int r_pix_buff[dMAX_PIXOUTS]; // number of pixel buffer owned by reading process
int last_pix_buff[dMAX_PIXOUTS]; // number of the last pixel buffer written
volatile LONG pb_ready[dMAX_PIXOUTS][dNUM_PIXEL_BUFFS]; // if value is 0, this data has already been read; 1 = fresh data to read
int num_average[dMAX_PIXOUTS]; // number of spans to average to create the pixels
int avail_frames[dMAX_PIXOUTS]; // number of pixel frames currently available to average
int av_in_idx[dMAX_PIXOUTS]; // input index in averaging pixel buffer ring
int av_out_idx[dMAX_PIXOUTS]; // output index in averaging pixel buffer ring
double *av_sum[dMAX_PIXOUTS]; // pointer to sum buffer for averaging
double *av_buff[dMAX_PIXOUTS][dMAX_AVERAGE]; // pointers to ring of buffers to hold pixel frames for averaging
double *pre_av_out;
int av_mode[dMAX_PIXOUTS];
double av_backmult[dMAX_PIXOUTS]; // back multiplier for weighted averaging
double *cd; // pointer to amplitude calibration buffer
int n_freqs[dMAX_CAL_SETS]; // number of frequencies in each calibration set
double *freqs[dMAX_CAL_SETS]; // pointers to vectors of calibration frequencies
double (*ac3[dMAX_CAL_SETS][dMAX_M]); // pointers to amplitude interpolant coefficients
double (*ac2[dMAX_CAL_SETS][dMAX_M]);
double (*ac1[dMAX_CAL_SETS][dMAX_M]);
double (*ac0[dMAX_CAL_SETS][dMAX_M]);
fftw_plan plan[dMAX_STITCH][dMAX_NUM_FFT]; // fftw plans
fftw_plan Cplan[dMAX_STITCH][dMAX_NUM_FFT];
double *fft_in[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to fftw real input vectors
fftw_complex *Cfft_in[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to fftw complex input vectors
fftw_complex *fft_out[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to fftw complex output vectors
volatile LONG *pnum_threads; // pointer to current number of active worker threads
int stop; // when set, fft threads will be returned to the pool
int end_dispatcher; // set this flag to one to destroy the dispatcher thread
volatile int dispatcher; // one if the dispatcher thread is alive & active
int ss; // sub-span being processed
int LO; // LO (within current sub-span) being processed
int flag;
int have_samples[dMAX_STITCH][dMAX_NUM_FFT]; // number of unused samples remaining in a buffer
int type; // 0 for REAL, 1 for COMPLEX
int incr; // size - overlap
int buff_size; // amount of data to be stored each time an input buffer is opened and closed = JanusAudio/BlockSize
dINREAL* I_samples[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to current input position in I/Q buffers
dINREAL* Q_samples[dMAX_STITCH][dMAX_NUM_FFT];
int bsize; // size of I_samples[][] and Q_samples[][] (number of samples they hold)
int IQout_index[dMAX_STITCH][dMAX_NUM_FFT]; // current output index for I_samples[ss][LO] and Q_samples[ss][LO]
int IQO_idx[dMAX_STITCH][dMAX_NUM_FFT];
int IQin_index[dMAX_STITCH][dMAX_NUM_FFT]; // current input index for I_samples[ss][LO] and Q_samples[ss][LO]
volatile LONG buff_ready[dMAX_STITCH][dMAX_NUM_FFT]; // 1 if buffer ready to read; 0 if needs to be filled
int max_writeahead; // max allowed input samples ahead of where reading output samples
volatile LONG snap[dMAX_STITCH][dMAX_NUM_FFT]; // set to 1 to allow a snap of raw spectrum data
HANDLE hSnapEvent[dMAX_STITCH][dMAX_NUM_FFT]; // mutex handles; mutexes will be used to signal a snap is complete
double *snap_buff[dMAX_STITCH][dMAX_NUM_FFT]; // pointers to buffers for the snap
HANDLE Sem_BuffReady; // signalled when input samples arrive, so the
// dispatcher can block instead of polling
CRITICAL_SECTION PB_ControlsSection[dMAX_PIXOUTS];
CRITICAL_SECTION SetAnalyzerSection;
CRITICAL_SECTION BufferControlSection[dMAX_STITCH][dMAX_NUM_FFT];
CRITICAL_SECTION StitchSection;
CRITICAL_SECTION EliminateSection[dMAX_STITCH];
CRITICAL_SECTION ResampleSection;
int det_type[dMAX_PIXOUTS]; // detector type
double inv_coherent_gain;
double inherent_power_gain;
double inv_enb;
double norm_oneHz; // dB factor to normalize to one Hz bandwidth
int sample_rate; // sample rate; used for normalization calculations
int normalize[dMAX_PIXOUTS];
// BEGIN CODE TO GET MAX FFT_BIN WITHIN A FREQUENCY RANGE
int dmb_run;
int dmb_disp;
int dmb_ss;
int dmb_LO;
double dmb_rate;
double dmb_fLow;
double dmb_fHigh;
double dmb_tau;
int dmb_frame_rate;
int dmb_begin0;
int dmb_end0;
int dmb_begin1;
int dmb_end1;
double dmb_decay;
double dmb_max_dB;
CRITICAL_SECTION cs_dmb;
// END CODE TO GET MAX FFT_BIN WITHIN A FREQUENCY RANGE
} dp, *DP;
extern DP pdisp[];
extern __declspec( dllexport )
void CreateAnalyzer ( int disp,
int *success,
char *app_data_path);
extern __declspec( dllexport )
void XCreateAnalyzer ( int disp,
int *success, //writes '0' to success if all went well, <0 if mem alloc failed
int m_size, //maximum fft size to be used
int m_LO, //maximum number of LO positions per subspan
int m_stitch, //maximum number of subspans to be concatenated
char *app_data_path
);
extern __declspec( dllexport )
void DestroyAnalyzer(int disp);
extern __declspec( dllexport )
void SetCalibration ( int disp,
int set_num, //identifier for this calibration data set
int n_points, //number of calibration points in the set
double (*cal)[dMAX_M+1] //pointer to the calibration table, first
);
extern __declspec( dllexport )
void OpenBuffer(int disp, int ss, int LO, void **Ipointer, void **Qpointer);
extern __declspec( dllexport )
void CloseBuffer(int disp, int ss, int LO);
extern __declspec( dllexport )
void Spectrum(int disp, int ss, int LO, dINREAL* pI, dINREAL* pQ);
extern __declspec( dllexport )
void Spectrum2(int run, int disp, int ss, int LO, dINREAL* pbuff);
extern __declspec( dllexport )
void Spectrum0(int run, int disp, int ss, int LO, double* pbuff);
extern __declspec( dllexport )
void SnapSpectrum( int disp,
int ss,
int LO,
double *snap_buff);
extern __declspec( dllexport )
void SnapSpectrumTimeout (int disp,
int ss,
int LO,
double* snap_buff,
DWORD timeout,
int* flag);
#endif