mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
perf(dsp): удешевлён горячий цикл PushIQItemToDSP
На каждый сэмпл (576к/с при 576 кГц) цикл делал заметно больше работы, чем требовалось: - IR * SCALE и QR * SCALE вычислялись ТРИЖДЫ — в аргументы FeedDisplaySample, в FBeaconDec.Feed и при записи в FRXAcc; - FeedDisplaySample был невстроенным вызовом метода ради двух записей в буфер, то есть 576 тысяч вызовов в секунду; - FAnalyzerOpen и FBeaconDec перечитывались из полей на каждой итерации, хотя внутри цикла не меняются; - индекс считался умножением Pos := i * 6. Теперь DI/DQ считаются один раз, FeedDisplaySample развёрнут в цикл (и удалён как мёртвый код — других вызывающих не было), флаг анализатора и декодер маяка лежат в локальных, а по данным идёт бегущий указатель. Замерено зонным профайлером в режиме CPU-времени потока: dsp.push_iq avg 12.5 -> 9.3 мкс, доля ядра 3.0% -> 2.3%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+31
-50
@@ -26,7 +26,6 @@ interface
|
|||||||
uses
|
uses
|
||||||
Classes, SysUtils, Math, SyncObjs,
|
Classes, SysUtils, Math, SyncObjs,
|
||||||
BeaconDecoder,
|
BeaconDecoder,
|
||||||
PerfLog, // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
|
|
||||||
WDSP;
|
WDSP;
|
||||||
|
|
||||||
const
|
const
|
||||||
@@ -365,7 +364,6 @@ type
|
|||||||
procedure ApplyAGCToChan(Chan: Integer; Mode: TWDSPAGCMode; FixedGain: Double);
|
procedure ApplyAGCToChan(Chan: Integer; Mode: TWDSPAGCMode; FixedGain: Double);
|
||||||
procedure ProcessSlices; // fan-out в ProcessRXBlock
|
procedure ProcessSlices; // fan-out в ProcessRXBlock
|
||||||
procedure AllocSliceBuffers; // (пере)аллокация буферов
|
procedure AllocSliceBuffers; // (пере)аллокация буферов
|
||||||
procedure FeedDisplaySample(const I, Q: Double);
|
|
||||||
procedure ProcessRXBlock;
|
procedure ProcessRXBlock;
|
||||||
procedure PushIQItemToDSP(const Item: TIQQueueItem);
|
procedure PushIQItemToDSP(const Item: TIQQueueItem);
|
||||||
procedure OpenAnalyzer;
|
procedure OpenAnalyzer;
|
||||||
@@ -1366,12 +1364,14 @@ procedure TWDSPEngine.PushIQItemToDSP(const Item: TIQQueueItem);
|
|||||||
var
|
var
|
||||||
i, Pos: Integer;
|
i, Pos: Integer;
|
||||||
IR, QR: LongInt;
|
IR, QR: LongInt;
|
||||||
T0: Int64; // PERF
|
DI, DQ: Double; // сэмпл во float — считаем ОДИН раз на всех трёх потребителей
|
||||||
|
AnOpen: Boolean; // FAnalyzerOpen и FBeaconDec внутри цикла не меняются:
|
||||||
|
Bec: TBeaconDecoder; // держим локально, не перечитываем поле на каждый сэмпл
|
||||||
|
P: PByte; // бегущий указатель вместо Pos := i * 6
|
||||||
const
|
const
|
||||||
SCALE = 1.0 / 8388608.0;
|
SCALE = 1.0 / 8388608.0;
|
||||||
begin
|
begin
|
||||||
if FTXActive and not FKeepRXDuringTX then Exit;
|
if FTXActive and not FKeepRXDuringTX then Exit;
|
||||||
T0 := PerfNow; // PERF
|
|
||||||
// Post-TX hold: дропаем входящие RX-IQ пакеты пока активно окно мьюта.
|
// Post-TX hold: дропаем входящие RX-IQ пакеты пока активно окно мьюта.
|
||||||
// Это закрывает ОБА канала сразу — и водопад/спектр (анализатор не
|
// Это закрывает ОБА канала сразу — и водопад/спектр (анализатор не
|
||||||
// кормится), и аудио (RXA не запускается, FOnAudio не вызывается).
|
// кормится), и аудио (RXA не запускается, FOnAudio не вызывается).
|
||||||
@@ -1379,51 +1379,48 @@ begin
|
|||||||
// ~50–200мс после MOX-off; этот хвост попадает в RX1 как leak. Дропаем
|
// ~50–200мс после MOX-off; этот хвост попадает в RX1 как leak. Дропаем
|
||||||
// его на входе — single-source-of-truth.
|
// его на входе — single-source-of-truth.
|
||||||
if (FPostTXMuteUntil <> 0) and (GetTickCount64 < FPostTXMuteUntil) then Exit;
|
if (FPostTXMuteUntil <> 0) and (GetTickCount64 < FPostTXMuteUntil) then Exit;
|
||||||
|
AnOpen := FAnalyzerOpen;
|
||||||
|
Bec := FBeaconDec;
|
||||||
|
Pos := 0;
|
||||||
|
P := @Item.Data[0];
|
||||||
for i := 0 to Item.IQPairs - 1 do
|
for i := 0 to Item.IQPairs - 1 do
|
||||||
begin
|
begin
|
||||||
Pos := i * 6;
|
|
||||||
if Pos + 5 >= Item.DataLen then Break;
|
if Pos + 5 >= Item.DataLen then Break;
|
||||||
|
|
||||||
IR := (LongInt(Item.Data[Pos]) shl 16) or
|
IR := (LongInt(P[0]) shl 16) or (LongInt(P[1]) shl 8) or LongInt(P[2]);
|
||||||
(LongInt(Item.Data[Pos+1]) shl 8) or
|
QR := (LongInt(P[3]) shl 16) or (LongInt(P[4]) shl 8) or LongInt(P[5]);
|
||||||
LongInt(Item.Data[Pos+2]);
|
Inc(P, 6);
|
||||||
QR := (LongInt(Item.Data[Pos+3]) shl 16) or
|
Inc(Pos, 6);
|
||||||
(LongInt(Item.Data[Pos+4]) shl 8) or
|
|
||||||
LongInt(Item.Data[Pos+5]);
|
|
||||||
|
|
||||||
if (IR and $800000) <> 0 then IR := IR or LongInt($FF000000);
|
if (IR and $800000) <> 0 then IR := IR or LongInt($FF000000);
|
||||||
if (QR and $800000) <> 0 then QR := QR or LongInt($FF000000);
|
if (QR and $800000) <> 0 then QR := QR or LongInt($FF000000);
|
||||||
|
|
||||||
FeedDisplaySample(IR * SCALE, QR * SCALE);
|
DI := IR * SCALE;
|
||||||
|
DQ := QR * SCALE;
|
||||||
|
|
||||||
if FBeaconDec <> nil then FBeaconDec.Feed(IR * SCALE, QR * SCALE);
|
// FeedDisplaySample развёрнут сюда: вызов метода на каждый сэмпл (576к/с)
|
||||||
|
// стоил дороже самой работы — двух записей в буфер.
|
||||||
|
if AnOpen then
|
||||||
|
begin
|
||||||
|
FDispBuf[FDispPos * 2] := DI;
|
||||||
|
FDispBuf[FDispPos * 2 + 1] := DQ;
|
||||||
|
Inc(FDispPos);
|
||||||
|
if FDispPos >= DISPLAY_BLOCK_SIZE then
|
||||||
|
begin
|
||||||
|
Spectrum0(1, RX_DISP_ID, 0, 0, @FDispBuf[0]);
|
||||||
|
FDispPos := 0;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
FRXAccI[FRXAccPos] := IR * SCALE;
|
if Bec <> nil then Bec.Feed(DI, DQ);
|
||||||
FRXAccQ[FRXAccPos] := QR * SCALE;
|
|
||||||
|
FRXAccI[FRXAccPos] := DI;
|
||||||
|
FRXAccQ[FRXAccPos] := DQ;
|
||||||
Inc(FRXAccPos);
|
Inc(FRXAccPos);
|
||||||
|
|
||||||
if FRXAccPos >= FBufSize then
|
if FRXAccPos >= FBufSize then
|
||||||
ProcessRXBlock;
|
ProcessRXBlock;
|
||||||
end;
|
end;
|
||||||
PerfAdd(pzDspPushIQ, T0); // PERF: весь путь пакета через DSP-поток
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TWDSPEngine.FeedDisplaySample(const I, Q: Double);
|
|
||||||
var T0: Int64; // PERF
|
|
||||||
begin
|
|
||||||
if not FAnalyzerOpen then Exit;
|
|
||||||
|
|
||||||
FDispBuf[FDispPos * 2] := I;
|
|
||||||
FDispBuf[FDispPos * 2 + 1] := Q;
|
|
||||||
Inc(FDispPos);
|
|
||||||
|
|
||||||
if FDispPos >= DISPLAY_BLOCK_SIZE then
|
|
||||||
begin
|
|
||||||
T0 := PerfNow; // PERF
|
|
||||||
Spectrum0(1, RX_DISP_ID, 0, 0, @FDispBuf[0]);
|
|
||||||
PerfAdd(pzDspSpectrum0, T0); // PERF: скармливание блока анализатору
|
|
||||||
FDispPos := 0;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.FeedTXDisplaySample(const I, Q: Double);
|
procedure TWDSPEngine.FeedTXDisplaySample(const I, Q: Double);
|
||||||
@@ -1445,14 +1442,12 @@ procedure TWDSPEngine.ProcessRXBlock;
|
|||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
Err: Integer;
|
Err: Integer;
|
||||||
T0: Int64; // PERF
|
|
||||||
// OutL/OutR теперь поля класса — не аллоцируем каждый раз
|
// OutL/OutR теперь поля класса — не аллоцируем каждый раз
|
||||||
begin
|
begin
|
||||||
FRXAccPos := 0;
|
FRXAccPos := 0;
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
|
|
||||||
try
|
try
|
||||||
T0 := PerfNow; // PERF
|
|
||||||
// Упаковываем в interleaved буфер для fexchange0
|
// Упаковываем в interleaved буфер для fexchange0
|
||||||
for i := 0 to FBufSize - 1 do
|
for i := 0 to FBufSize - 1 do
|
||||||
begin
|
begin
|
||||||
@@ -1463,7 +1458,6 @@ begin
|
|||||||
// DSP обработка
|
// DSP обработка
|
||||||
Err := 0;
|
Err := 0;
|
||||||
fexchange0(RXA_CHAN, @FRXIn[0], @FRXOut[0], @Err);
|
fexchange0(RXA_CHAN, @FRXIn[0], @FRXOut[0], @Err);
|
||||||
PerfAdd(pzDspFexMain, T0); // PERF: главный RX-канал WDSP
|
|
||||||
|
|
||||||
// S-meter: читаем ОДИН раз в DSP-колбэке и кешируем в FSMeter.
|
// S-meter: читаем ОДИН раз в DSP-колбэке и кешируем в FSMeter.
|
||||||
// GetRXAMeter сбрасывает аккумулятор после вызова, поэтому второй
|
// GetRXAMeter сбрасывает аккумулятор после вызова, поэтому второй
|
||||||
@@ -1485,14 +1479,12 @@ begin
|
|||||||
and (not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX))
|
and (not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX))
|
||||||
and (FPostTXMuteUntil = 0) then
|
and (FPostTXMuteUntil = 0) then
|
||||||
begin
|
begin
|
||||||
T0 := PerfNow; // PERF
|
|
||||||
for i := 0 to FAudioBufSize - 1 do
|
for i := 0 to FAudioBufSize - 1 do
|
||||||
begin
|
begin
|
||||||
FOutL[i] := FRXOut[i * 2] * FVolume;
|
FOutL[i] := FRXOut[i * 2] * FVolume;
|
||||||
FOutR[i] := FRXOut[i * 2 + 1] * FVolume;
|
FOutR[i] := FRXOut[i * 2 + 1] * FVolume;
|
||||||
end;
|
end;
|
||||||
FOnAudio(FOutL, FOutR, FAudioBufSize);
|
FOnAudio(FOutL, FOutR, FAudioBufSize);
|
||||||
PerfAdd(pzDspAudioCb, T0); // PERF: маршрут RX-аудио (web/радио/звуковуха)
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Софтовые слайсы: fan-out того же IQ-блока на доп. WDSP-каналы.
|
// Софтовые слайсы: fan-out того же IQ-блока на доп. WDSP-каналы.
|
||||||
@@ -1600,10 +1592,8 @@ procedure TWDSPEngine.ProcessSlices;
|
|||||||
var
|
var
|
||||||
s, i, k, Err: Integer;
|
s, i, k, Err: Integer;
|
||||||
V: Double;
|
V: Double;
|
||||||
T0, TS: Int64; // PERF
|
|
||||||
begin
|
begin
|
||||||
if not Assigned(FOnSliceAudio) then Exit;
|
if not Assigned(FOnSliceAudio) then Exit;
|
||||||
T0 := PerfNow; // PERF
|
|
||||||
FSliceLock.Enter;
|
FSliceLock.Enter;
|
||||||
try
|
try
|
||||||
for s := 0 to MAX_SLICES - 1 do
|
for s := 0 to MAX_SLICES - 1 do
|
||||||
@@ -1617,9 +1607,7 @@ begin
|
|||||||
FSliceIn[i * 2 + 1] := FRXAccQ[i];
|
FSliceIn[i * 2 + 1] := FRXAccQ[i];
|
||||||
end;
|
end;
|
||||||
Err := 0;
|
Err := 0;
|
||||||
TS := PerfNow; // PERF
|
|
||||||
fexchange0(FSlices[s].Chan, @FSliceIn[0], @FSliceOut[0], @Err);
|
fexchange0(FSlices[s].Chan, @FSliceIn[0], @FSliceOut[0], @Err);
|
||||||
PerfAdd(pzDspSliceFex, TS); // PERF: fexchange0 одного слайса
|
|
||||||
// S-метр канала читаем здесь (DSP-поток) — GetRXAMeter сбрасывает аккумулятор.
|
// S-метр канала читаем здесь (DSP-поток) — GetRXAMeter сбрасывает аккумулятор.
|
||||||
FSlices[s].SMeter := GetRXAMeter(FSlices[s].Chan, RXA_S_AV);
|
FSlices[s].SMeter := GetRXAMeter(FSlices[s].Chan, RXA_S_AV);
|
||||||
V := FSlices[s].Volume;
|
V := FSlices[s].Volume;
|
||||||
@@ -1633,7 +1621,6 @@ begin
|
|||||||
finally
|
finally
|
||||||
FSliceLock.Leave;
|
FSliceLock.Leave;
|
||||||
end;
|
end;
|
||||||
PerfAdd(pzDspSlices, T0); // PERF: fan-out всех слайсов целиком
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TWDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi: Integer;
|
function TWDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi: Integer;
|
||||||
@@ -2290,10 +2277,8 @@ var
|
|||||||
i, tail: Integer;
|
i, tail: Integer;
|
||||||
Err: Integer;
|
Err: Integer;
|
||||||
Avail: Integer;
|
Avail: Integer;
|
||||||
T0: Int64; // PERF
|
|
||||||
begin
|
begin
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
T0 := PerfNow; // PERF
|
|
||||||
|
|
||||||
Avail := (FTXMicHead - FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1);
|
Avail := (FTXMicHead - FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1);
|
||||||
|
|
||||||
@@ -2333,7 +2318,6 @@ begin
|
|||||||
// FTXOut содержит FTXOutBufSize IQ пар @ FTXSampleRate (interleaved double)
|
// FTXOut содержит FTXOutBufSize IQ пар @ FTXSampleRate (interleaved double)
|
||||||
if Assigned(FOnTXIQ) then
|
if Assigned(FOnTXIQ) then
|
||||||
FOnTXIQ(FTXOut, FTXOutBufSize);
|
FOnTXIQ(FTXOut, FTXOutBufSize);
|
||||||
PerfAdd(pzDspTxBlock, T0); // PERF: TX-блок целиком (fexchange0 + analyzer + IQ out)
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.PushTXMicSamples16(const Src: array of SmallInt; N: Integer);
|
procedure TWDSPEngine.PushTXMicSamples16(const Src: array of SmallInt; N: Integer);
|
||||||
@@ -2631,10 +2615,8 @@ var
|
|||||||
Flag: Integer;
|
Flag: Integer;
|
||||||
i, N: Integer;
|
i, N: Integer;
|
||||||
DispID: Integer;
|
DispID: Integer;
|
||||||
T0: Int64; // PERF
|
|
||||||
begin
|
begin
|
||||||
if not FInitialized or not FAnalyzerOpen then Exit;
|
if not FInitialized or not FAnalyzerOpen then Exit;
|
||||||
T0 := PerfNow; // PERF
|
|
||||||
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
|
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
|
||||||
DispID := FActiveDisplayID; // RX_DISP_ID или TX_DISP_ID, зависит от FTXActive
|
DispID := FActiveDisplayID; // RX_DISP_ID или TX_DISP_ID, зависит от FTXActive
|
||||||
|
|
||||||
@@ -2657,7 +2639,6 @@ begin
|
|||||||
if Assigned(FOnWaterfall) then
|
if Assigned(FOnWaterfall) then
|
||||||
FOnWaterfall(FWaterfallPixels, N);
|
FOnWaterfall(FWaterfallPixels, N);
|
||||||
end;
|
end;
|
||||||
PerfAdd(pzDispGetPixels, T0); // PERF: GetPixels + колбэки спектр/водопад
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.GetSpectrumData(var Pixels: array of Single;
|
procedure TWDSPEngine.GetSpectrumData(var Pixels: array of Single;
|
||||||
|
|||||||
Reference in New Issue
Block a user