chore: strip temporary PerfLog instrumentation

Профилирование завершено (кадр спектра 8.3мс -> 1.7мс, UI 45% -> 26% ядра):
удаляем PerfLog.pas, doc/PERF_PROFILING.md и все // PERF-замеры из
WDSPEngine/HPSDRNetwork/PlutoBackend/AudioOutput/SpectrumView(+GL)/
WaterfallViewOpengl/WidebandView/MainForm. Сами оптимизации (full-raw
рендерер, дворд-блендеры, порог бэндплана) остаются.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 00:36:03 +03:00
co-authored by Claude Fable 5
parent 0adaff2d21
commit 01d78d148f
12 changed files with 10 additions and 521 deletions
+1 -5
View File
@@ -21,8 +21,7 @@ unit AudioOutput;
interface interface
uses uses
Classes, SysUtils, Math, SyncObjs, DynLibs, Classes, SysUtils, Math, SyncObjs, DynLibs;
PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
const const
MY_AUDIO_BUFFER_SIZE = 128; // default PA frames per callback — как piHPSDR MY_AUDIO_BUFFER_SIZE = 128; // default PA frames per callback — как piHPSDR
@@ -629,13 +628,10 @@ procedure TAudioOutput.Write(const Left, Right: array of Single; Count: Integer)
// Порядок: сначала пишем данные в FBuf, потом обновляем FInPt (memory barrier) // Порядок: сначала пишем данные в FBuf, потом обновляем FInPt (memory barrier)
var var
i: Integer; i: Integer;
T0: Int64; // PERF
begin begin
if not FOpen then Exit; if not FOpen then Exit;
T0 := PerfNow; // PERF
for i := 0 to Count - 1 do for i := 0 to Count - 1 do
WriteDouble(Left[i], Right[i]); WriteDouble(Left[i], Right[i]);
PerfAdd(pzAudioWrite, T0); // PERF: запись блока в PA ring (все экземпляры)
end; end;
end. end.
+1 -8
View File
@@ -19,8 +19,7 @@ uses
{$ELSE} {$ELSE}
Sockets, BaseUnix, Sockets, BaseUnix,
{$ENDIF} {$ENDIF}
HPSDRProtocol, SyncObjs, Settings, RadioBackend, HPSDRProtocol, SyncObjs, Settings, RadioBackend;
PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
const const
DUC_TX_QUEUE_SIZE = 256; // power of two, bounded latency on TX underrun/overrun DUC_TX_QUEUE_SIZE = 256; // power of two, bounded latency on TX underrun/overrun
@@ -1094,14 +1093,11 @@ procedure THPSDRNetwork.HandleDDCIQ(const Buf: array of Byte; Len: Integer;
DDCIdx: Integer); DDCIdx: Integer);
var var
Pkt: TDDCIQPacket; Pkt: TDDCIQPacket;
T0: Int64; // PERF
begin begin
if not Assigned(FOnDDCIQ) then Exit; if not Assigned(FOnDDCIQ) then Exit;
T0 := PerfNow; // PERF
FillChar(Pkt, SizeOf(Pkt), 0); FillChar(Pkt, SizeOf(Pkt), 0);
Move(Buf[0], Pkt, Min(Len, SizeOf(Pkt))); Move(Buf[0], Pkt, Min(Len, SizeOf(Pkt)));
FOnDDCIQ(DDCIdx, Pkt); // прямой вызов из потока — UI не трогает FOnDDCIQ(DDCIdx, Pkt); // прямой вызов из потока — UI не трогает
PerfAdd(pzNetDDCIQ, T0); // PERF: копия пакета + постановка в DSP-очередь
end; end;
procedure THPSDRNetwork.HandleMicData(const Buf: array of Byte; Len: Integer); procedure THPSDRNetwork.HandleMicData(const Buf: array of Byte; Len: Integer);
@@ -1118,11 +1114,9 @@ procedure THPSDRNetwork.HandleWidebandData(const Buf: array of Byte; Len: Intege
var var
Seq: LongWord; Seq: LongWord;
I, SamplesInPacket, TargetCount, S: Integer; I, SamplesInPacket, TargetCount, S: Integer;
T0: Int64; // PERF
begin begin
if (not FWidebandEnabled) or (ADCIdx <> FWidebandADC) or if (not FWidebandEnabled) or (ADCIdx <> FWidebandADC) or
(FWBSampleBits <> 16) or (Len < 6) then Exit; (FWBSampleBits <> 16) or (Len < 6) then Exit;
T0 := PerfNow; // PERF
SamplesInPacket := (Len - 4) div 2; SamplesInPacket := (Len - 4) div 2;
if SamplesInPacket <= 0 then Exit; if SamplesInPacket <= 0 then Exit;
@@ -1163,7 +1157,6 @@ begin
FWBFrameCount := 0; FWBFrameCount := 0;
FWBSeqValid := False; FWBSeqValid := False;
end; end;
PerfAdd(pzNetWideband, T0); // PERF: сборка wideband-кадра + колбэк
end; end;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
-21
View File
@@ -37,7 +37,6 @@ uses
WidebandView, WidebandView,
StatusBar, StatusBar,
PlatformUtils, PlatformUtils,
PerfLog, // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
WinFirewall, WinFirewall,
BoardUtils, WisdomBuilder, UISync, BoardUtils, WisdomBuilder, UISync,
FlatEdit, FMRepeater, FlatEdit, FMRepeater,
@@ -3169,10 +3168,7 @@ var
PeakTarget, MinTarget: Double; PeakTarget, MinTarget: Double;
PeakDiff, MinDiff: Double; PeakDiff, MinDiff: Double;
PeakAlpha, MinAlpha: Double; PeakAlpha, MinAlpha: Double;
PerfT0: Int64; // PERF
begin begin
PerfT0 := PerfNow; // PERF
try
if not FController.FRunning then if not FController.FRunning then
begin begin
if PbSMeterRight <> nil then PbSMeterRight.Invalidate; if PbSMeterRight <> nil then PbSMeterRight.Invalidate;
@@ -3226,9 +3222,6 @@ begin
else else
SetStatusText(5, 'TX idle'); SetStatusText(5, 'TX idle');
end; end;
finally
PerfAdd(pzUiMeterTick, PerfT0); // PERF: тик метра целиком (10 Гц)
end;
end; end;
procedure TMainForm.SpectrumTimerTick(Sender: TObject); procedure TMainForm.SpectrumTimerTick(Sender: TObject);
@@ -3236,10 +3229,7 @@ var
NowTick: QWord; NowTick: QWord;
ElapsedTick: QWord; ElapsedTick: QWord;
I: Integer; I: Integer;
PerfT0, PerfTz: Int64; // PERF
begin begin
PerfT0 := PerfNow; // PERF
try
// Синхронизируем размеры если размер панели изменился (после ресайза). // Синхронизируем размеры если размер панели изменился (после ресайза).
// Важно: проверяем только видимые панели — когда спектр скрыт, // Важно: проверяем только видимые панели — когда спектр скрыт,
// PbSpectrum.Height может быть ненулевым при FSpectrumHeight=0, // PbSpectrum.Height может быть ненулевым при FSpectrumHeight=0,
@@ -3312,16 +3302,13 @@ begin
// Обновляем оверлей если видим // Обновляем оверлей если видим
if Assigned(FVfoOverlay) and FVfoOverlay.Visible then if Assigned(FVfoOverlay) and FVfoOverlay.Visible then
begin begin
PerfTz := PerfNow; // PERF
FVfoOverlay.UpdateSMeter(FController.FLastSMeter); FVfoOverlay.UpdateSMeter(FController.FLastSMeter);
FVfoOverlay.UpdateVfo(FController.FVfoA); FVfoOverlay.UpdateVfo(FController.FVfoA);
PushVfoOverlayExtState; PushVfoOverlayExtState;
PositionVfoOverlay; PositionVfoOverlay;
PerfAdd(pzUiVfoOverlay, PerfTz); // PERF: обновление главного VFO-флага
end; end;
if Length(FSliceFlags) > 0 then if Length(FSliceFlags) > 0 then
begin begin
PerfTz := PerfNow; // PERF
PositionSliceFlags; // держим слайс-флаги на частоте PositionSliceFlags; // держим слайс-флаги на частоте
// S-метр слайсов троттлим до ~10 Гц: каждое изменение метра = софтовый // S-метр слайсов троттлим до ~10 Гц: каждое изменение метра = софтовый
// ре-рендер флага (дорого), 60 Гц там не нужно. // ре-рендер флага (дорого), 60 Гц там не нужно.
@@ -3332,7 +3319,6 @@ begin
if FSliceFlags[I] <> nil then if FSliceFlags[I] <> nil then
FSliceFlags[I].UpdateSMeter(FController.SliceSMeter(FSliceFlags[I].SliceId)); FSliceFlags[I].UpdateSMeter(FController.SliceSMeter(FSliceFlags[I].SliceId));
end; end;
PerfAdd(pzUiSliceFlags, PerfTz); // PERF: позиционирование + S-метры слайс-флагов
end; end;
// Панорама и водопад теперь рисуются независимо по факту новых кадров. // Панорама и водопад теперь рисуются независимо по факту новых кадров.
// Это лучше совпадает с Thetis: маленький FFT даёт новые строки чаще. // Это лучше совпадает с Thetis: маленький FFT даёт новые строки чаще.
@@ -3399,14 +3385,7 @@ begin
// Исходящее зеркало состояния + спектр/водопад в web — в адаптере (всё из // Исходящее зеркало состояния + спектр/водопад в web — в адаптере (всё из
// FController, включая последний кадр пикселей). Сервер сам гейтит по FRunning. // FController, включая последний кадр пикселей). Сервер сам гейтит по FRunning.
if Assigned(FWebAdapter) then if Assigned(FWebAdapter) then
begin
PerfTz := PerfNow; // PERF
FWebAdapter.PushState; FWebAdapter.PushState;
PerfAdd(pzUiWebPush, PerfTz); // PERF: web-зеркало состояния + кадры клиентам
end;
finally
PerfAdd(pzUiSpectrumTick, PerfT0); // PERF: тик спектра целиком (кадровый таймер)
end;
end; end;
// =========================================================================== // ===========================================================================
-315
View File
@@ -1,315 +0,0 @@
unit PerfLog;
{ ===========================================================================
PerfLog лёгкий зонный CPU-профилировщик для поиска горячих точек.
ВРЕМЕННЫЙ ОТЛАДОЧНЫЙ МОДУЛЬ (ветка perf/cpu-profiling) не мержить в main.
Использование в горячем коде:
var t0: Int64;
...
t0 := PerfNow;
<работа>
PerfAdd(pzDspFexMain, t0);
Накладные расходы: два чтения монотонных часов + несколько сложений.
Никаких строк/аллокаций/локов в горячем пути. Каждая зона пишется из
одного потока (single-writer), поэтому обычные Int64-инкременты безопасны;
репортер читает значения racy для отладки это приемлемо.
Отчёт раз в PERF_REPORT_SEC секунд в StdErr (и в файл, если задан
EWSDR_PERF_FILE). Отключение: переменная окружения EWSDR_PERF=0.
Колонки отчёта:
calls вызовов за интервал
c/s вызовов в секунду
avg среднее время на вызов, мкс
max максимум за интервал, мкс
total суммарно за интервал, мс
core% доля одного ядра CPU (total / интервал * 100)
=========================================================================== }
{$mode objfpc}{$H+}
interface
type
TPerfZone = (
// Сетевой поток (приём UDP от радио)
pzNetDDCIQ, // HandleDDCIQ: разбор IQ-пакета + постановка в очередь
pzNetWideband, // HandleWidebandData: wideband-пакеты ADC
// Pluto (libiio, свои RX/TX потоки — мимо HPSDRNetwork)
pzPlutoRxConv, // RX: конверсия iio-буфера int16→24bit BE + FOnDDCIQ
pzPlutoTxResamp, // TX: полифазный интерполятор 192k→device-rate + FIFO
pzPlutoTxFill, // TX: наполнение iio-буфера из FIFO (без блокирующего push)
// DSP-поток (WDSPEngine)
pzDspPushIQ, // PushIQItemToDSP целиком (декод 24-бит + акк + всё ниже)
pzDspSpectrum0, // Spectrum0() — скармливание display-блока анализатору
pzDspFexMain, // fexchange0 главного RX-канала
pzDspAudioCb, // копирование аудио + FOnAudio (маршрут аудио)
pzDspSlices, // ProcessSlices целиком (все слайсы)
pzDspSliceFex, // fexchange0 одного слайс-канала
pzDspTxBlock, // ProcessTXBlock (TX-тракт)
// Display-поток (WDSPEngine.UpdateSpectrum)
pzDispGetPixels, // GetPixels спектр+водопад из анализатора WDSP
// Аудио-выход (PortAudio ring, из DSP-потока)
pzAudioWrite, // TAudioOutput.Write (все экземпляры: главный + слайсы)
// UI-поток (главная форма, таймеры, отрисовка)
pzUiSpectrumTick, // SpectrumTimerTick целиком
pzUiMeterTick, // MeterTimerTick целиком
pzUiDrawSpectrum, // SpectrumView.DrawSpectrum (CPU или GL)
pzUiDrawWaterfall, // SpectrumView.DrawWaterfall
pzUiPaintSpectrum, // PaintSpectrum: блит битмапа на канву (Qt6!)
pzUiPaintWaterfall,// PaintWaterfall: блит битмапа на канву (Qt6!)
pzUiSliceFlags, // обновление S-метров слайс-флагов (ре-рендер флага)
pzUiVfoOverlay, // обновление VFO-оверлея (S-метр/частота/позиция)
pzUiWidebandPaint, // отрисовка wideband-панорамы
pzUiWebPush, // WebAdapter.PushState (зеркало состояния + кадры в web)
// Разбивка CPU-рендера спектра (внутри ui.draw_spectrum)
pzUiSpecGrid, // ребилд кэша сетки + полный memcpy сетки в битмап
pzUiSpecBands, // полоса фильтра + слайс-маркеры + AGC-линии (BlendBand)
pzUiSpecCurve, // расчёт точек кривой + градиент под кривой + Polyline
pzUiSpecOverlays, // края/VFO/маркеры/бэндплан/флаги (DrawOverlay в битмап)
pzUiSpecAlpha, // финальный альфа-OR проход по всему битмапу
pzUiGlSwap, // SwapBuffers (GL) — включает ожидание vsync!
pzUiComposites // композиты бэндплан/панель/флаги (внутри raw-лока)
);
const
PERF_REPORT_SEC = 5;
PerfZoneName: array[TPerfZone] of string = (
'net.ddc_iq',
'net.wideband',
'pluto.rx_conv',
'pluto.tx_resamp',
'pluto.tx_fill',
'dsp.push_iq',
'dsp.spectrum0',
'dsp.fex_main',
'dsp.audio_cb',
'dsp.slices',
'dsp.slice_fex',
'dsp.tx_block',
'disp.get_pixels',
'audio.write',
'ui.spectrum_tick',
'ui.meter_tick',
'ui.draw_spectrum',
'ui.draw_waterfall',
'ui.paint_spectrum',
'ui.paint_waterfall',
'ui.slice_flags',
'ui.vfo_overlay',
'ui.wideband_paint',
'ui.web_push',
'ui.spec_grid',
'ui.spec_bands',
'ui.spec_curve',
'ui.spec_overlays',
'ui.spec_alpha',
'ui.gl_swap',
'ui.composites'
);
var
PerfEnabled: Boolean = True;
// Монотонные микросекунды.
function PerfNow: Int64;
// Прибавить (PerfNow - StartUs) к зоне. Вызывать в конце измеряемого участка.
procedure PerfAdd(Zone: TPerfZone; StartUs: Int64);
implementation
uses
SysUtils, Classes
{$IFDEF WINDOWS}, Windows{$ENDIF}
{$IFDEF LINUX}, BaseUnix, Linux{$ENDIF}
{$IF DEFINED(UNIX) AND NOT DEFINED(LINUX)}, BaseUnix, Unix{$ENDIF};
type
TZoneAcc = record
Count: Int64;
TotalUs: Int64;
MaxUs: Int64;
end;
var
Zones: array[TPerfZone] of TZoneAcc;
{$IFDEF WINDOWS}
var
QPCFreq: Int64 = 0;
function PerfNow: Int64;
var C: Int64;
begin
if QPCFreq = 0 then QueryPerformanceFrequency(QPCFreq);
QueryPerformanceCounter(C);
Result := (C * 1000000) div QPCFreq;
end;
{$ELSE}
{$IFDEF LINUX}
function PerfNow: Int64;
var ts: TTimeSpec;
begin
clock_gettime(CLOCK_MONOTONIC, @ts);
Result := Int64(ts.tv_sec) * 1000000 + ts.tv_nsec div 1000;
end;
{$ELSE}
function PerfNow: Int64;
var tv: TTimeVal;
begin
fpGetTimeOfDay(@tv, nil);
Result := Int64(tv.tv_sec) * 1000000 + tv.tv_usec;
end;
{$ENDIF}
{$ENDIF}
procedure PerfAdd(Zone: TPerfZone; StartUs: Int64);
var El: Int64;
begin
if not PerfEnabled then Exit;
El := PerfNow - StartUs;
if El < 0 then El := 0;
Inc(Zones[Zone].Count);
Inc(Zones[Zone].TotalUs, El);
if El > Zones[Zone].MaxUs then Zones[Zone].MaxUs := El;
end;
// ---------------------------------------------------------------------------
// Репортер
// ---------------------------------------------------------------------------
type
TPerfReporter = class(TThread)
private
FFile: TextFile;
FFileOpen: Boolean;
procedure Emit(const S: string);
procedure Report(ElapsedUs: Int64);
protected
procedure Execute; override;
public
constructor Create;
end;
var
Reporter: TPerfReporter = nil;
constructor TPerfReporter.Create;
var FN: string;
begin
FFileOpen := False;
FN := GetEnvironmentVariable('EWSDR_PERF_FILE');
if FN <> '' then
begin
AssignFile(FFile, FN);
try
Rewrite(FFile);
FFileOpen := True;
except
FFileOpen := False;
end;
end;
FreeOnTerminate := False;
inherited Create(False);
end;
procedure TPerfReporter.Emit(const S: string);
begin
try
WriteLn(StdErr, S);
except
end;
if FFileOpen then
begin
WriteLn(FFile, S);
Flush(FFile);
end;
end;
procedure TPerfReporter.Report(ElapsedUs: Int64);
var
Z: TPerfZone;
Snap: array[TPerfZone] of TZoneAcc;
Sec: Double;
Any: Boolean;
begin
// Снимок + сброс (racy, но для отладки достаточно).
for Z := Low(TPerfZone) to High(TPerfZone) do
begin
Snap[Z] := Zones[Z];
Zones[Z].Count := 0;
Zones[Z].TotalUs := 0;
Zones[Z].MaxUs := 0;
end;
Any := False;
for Z := Low(TPerfZone) to High(TPerfZone) do
if Snap[Z].Count > 0 then begin Any := True; Break; end;
if not Any then Exit;
Sec := ElapsedUs / 1000000.0;
Emit(Format('[PERF] ===== interval %.2f s =====', [Sec]));
Emit(Format('[PERF] %-18s %8s %8s %8s %8s %9s %6s',
['zone', 'calls', 'c/s', 'avg_us', 'max_us', 'total_ms', 'core%']));
for Z := Low(TPerfZone) to High(TPerfZone) do
if Snap[Z].Count > 0 then
Emit(Format('[PERF] %-18s %8d %8.0f %8.1f %8d %9.1f %6.1f',
[PerfZoneName[Z],
Snap[Z].Count,
Snap[Z].Count / Sec,
Snap[Z].TotalUs / Snap[Z].Count,
Snap[Z].MaxUs,
Snap[Z].TotalUs / 1000.0,
Snap[Z].TotalUs / ElapsedUs * 100.0]));
end;
procedure TPerfReporter.Execute;
var
LastUs, NowUs: Int64;
Waited: Integer;
begin
LastUs := PerfNow;
while not Terminated do
begin
// Спим мелкими шагами, чтобы быстро завершаться.
Waited := 0;
while (Waited < PERF_REPORT_SEC * 1000) and not Terminated do
begin
Sleep(100);
Inc(Waited, 100);
end;
if Terminated then Break;
NowUs := PerfNow;
Report(NowUs - LastUs);
LastUs := NowUs;
end;
end;
initialization
if GetEnvironmentVariable('EWSDR_PERF') = '0' then
PerfEnabled := False;
if PerfEnabled then
Reporter := TPerfReporter.Create;
finalization
if Reporter <> nil then
begin
Reporter.Terminate;
Reporter.WaitFor;
if Reporter.FFileOpen then
CloseFile(Reporter.FFile);
Reporter.Free;
end;
end.
+1 -11
View File
@@ -26,8 +26,7 @@ interface
uses uses
Classes, SysUtils, ctypes, Classes, SysUtils, ctypes,
RadioBackend, IIOBindings, HPSDRProtocol, Settings, RadioBackend, IIOBindings, HPSDRProtocol, Settings;
PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
type type
{ TPlutoBackend } { TPlutoBackend }
@@ -196,7 +195,6 @@ var
outc: Integer; outc: Integer;
vi, vq: LongInt; vi, vq: LongInt;
u: LongWord; u: LongWord;
T0: Int64; // PERF
procedure FlushPkt; procedure FlushPkt;
begin begin
@@ -222,7 +220,6 @@ begin
if Terminated then Break; if Terminated then Break;
if n <= 0 then begin Sleep(2); Continue; end; if n <= 0 then begin Sleep(2); Continue; end;
T0 := PerfNow; // PERF: только конверсия, без блокирующего refill
step := iio_buffer_step(FBk.FRxBuf); step := iio_buffer_step(FBk.FRxBuf);
pFirst := iio_buffer_first(FBk.FRxBuf, FBk.FRxChI); pFirst := iio_buffer_first(FBk.FRxBuf, FBk.FRxChI);
pEnd := iio_buffer_end(FBk.FRxBuf); pEnd := iio_buffer_end(FBk.FRxBuf);
@@ -249,7 +246,6 @@ begin
Inc(p, step); Inc(p, step);
end; end;
if outc > 0 then FlushPkt; // хвост блока if outc > 0 then FlushPkt; // хвост блока
PerfAdd(pzPlutoRxConv, T0); // PERF: int16→24bit BE + FOnDDCIQ на весь iio-буфер
end; end;
end; end;
@@ -266,12 +262,10 @@ var
pFirst, pEnd, p: PByte; pFirst, pEnd, p: PByte;
h: Integer; h: Integer;
n: ptrint; n: ptrint;
T0: Int64; // PERF
begin begin
while not Terminated do while not Terminated do
begin begin
if FBk.FTxBuf = nil then begin Sleep(5); Continue; end; if FBk.FTxBuf = nil then begin Sleep(5); Continue; end;
T0 := PerfNow; // PERF
step := iio_buffer_step(FBk.FTxBuf); step := iio_buffer_step(FBk.FTxBuf);
pFirst := iio_buffer_first(FBk.FTxBuf, FBk.FTxChI); pFirst := iio_buffer_first(FBk.FTxBuf, FBk.FTxChI);
pEnd := iio_buffer_end(FBk.FTxBuf); pEnd := iio_buffer_end(FBk.FTxBuf);
@@ -297,7 +291,6 @@ begin
Inc(p, step); Inc(p, step);
end; end;
LeaveCriticalSection(FBk.FTxLock); LeaveCriticalSection(FBk.FTxLock);
PerfAdd(pzPlutoTxFill, T0); // PERF: наполнение буфера (без блокирующего push)
if Terminated then Break; if Terminated then Break;
n := iio_buffer_push(FBk.FTxBuf); // блокирует → пейсинг на device-rate n := iio_buffer_push(FBk.FTxBuf); // блокирует → пейсинг на device-rate
@@ -860,10 +853,8 @@ procedure TPlutoBackend.SendDUCIQ(const IData, QData: array of Integer);
var var
i, p, k, t, L, taps, base, n: Integer; i, p, k, t, L, taps, base, n: Integer;
ai, aq: Double; ai, aq: Double;
T0: Int64; // PERF
begin begin
if (not FTxStreaming) or (FTxRatio < 1) or (Length(FTxCoef) = 0) then Exit; if (not FTxStreaming) or (FTxRatio < 1) or (Length(FTxCoef) = 0) then Exit;
T0 := PerfNow; // PERF
n := Length(IData); n := Length(IData);
if Length(QData) < n then n := Length(QData); if Length(QData) < n then n := Length(QData);
L := FTxRatio; L := FTxRatio;
@@ -902,7 +893,6 @@ begin
finally finally
LeaveCriticalSection(FTxLock); LeaveCriticalSection(FTxLock);
end; end;
PerfAdd(pzPlutoTxResamp, T0); // PERF: полифазный интерполятор + 24→16 + FIFO
end; end;
procedure TPlutoBackend.ClearDUCIQQueue; procedure TPlutoBackend.ClearDUCIQQueue;
+1 -32
View File
@@ -21,8 +21,7 @@ uses
Classes, SysUtils, Graphics, GraphType, ExtCtrls, Controls, Math, Classes, SysUtils, Graphics, GraphType, ExtCtrls, Controls, Math,
AppTheme, AppTheme,
AlertOverlay, SampleRateOverlay, VfoOverlay, BandPlanOverlay, AlertOverlay, SampleRateOverlay, VfoOverlay, BandPlanOverlay,
WaterfallView, SMeterView, RulerView, WaterfallView, SMeterView, RulerView;
PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
const const
SV_CLR_BG = TColor($00101010); SV_CLR_BG = TColor($00101010);
@@ -1280,11 +1279,8 @@ var
LabelBandW, SrcCount: Integer; LabelBandW, SrcCount: Integer;
GridFreqS, GridLine, pixPerStep: Double; GridFreqS, GridLine, pixPerStep: Double;
N, gridMult: Integer; N, gridMult: Integer;
PerfT0, PerfTz: Int64; // PERF
begin begin
if FSpectrumBitmap = nil then Exit; if FSpectrumBitmap = nil then Exit;
PerfT0 := PerfNow; // PERF
PerfTz := PerfT0; // PERF
W := FSpectrumBitmap.Width; H := FSpectrumBitmap.Height; W := FSpectrumBitmap.Width; H := FSpectrumBitmap.Height;
if (W <= 0) or (H <= 0) then Exit; if (W <= 0) or (H <= 0) then Exit;
C := FSpectrumBitmap.Canvas; C := FSpectrumBitmap.Canvas;
@@ -1377,8 +1373,6 @@ begin
FGridBitmap.EndUpdate(False); FGridBitmap.EndUpdate(False);
{$ENDIF} {$ENDIF}
end; end;
PerfAdd(pzUiSpecGrid, PerfTz); // PERF: ребилд кэша сетки (обычно 0)
PerfTz := PerfNow; // PERF
// ═══ Фаза 0: чистые вычисления — битмап не трогаем ═══════════════════════ // ═══ Фаза 0: чистые вычисления — битмап не трогаем ═══════════════════════
// Дальше битмап обрабатывается тремя непрерывными фазами: raw (ScanLine) → // Дальше битмап обрабатывается тремя непрерывными фазами: raw (ScanLine) →
@@ -1431,23 +1425,17 @@ begin
end; end;
FSpPts[W] := Point(W-1, H); FSpPts[W+1] := Point(0, H); FSpPts[W] := Point(W-1, H); FSpPts[W+1] := Point(0, H);
PerfAdd(pzUiSpecCurve, PerfTz); // PERF: расчёт точек кривой
PerfTz := PerfNow; // PERF
// ═══ Фаза 1: raw — весь кадр одним локом ═════════════════════════════════ // ═══ Фаза 1: raw — весь кадр одним локом ═════════════════════════════════
{$IFDEF DARWIN} {$IFDEF DARWIN}
// Cocoa-вариант CopyGridToSpectrum идёт через Canvas.Draw — до RawBegin. // Cocoa-вариант CopyGridToSpectrum идёт через Canvas.Draw — до RawBegin.
CopyGridToSpectrum(W, H); CopyGridToSpectrum(W, H);
PerfAdd(pzUiSpecGrid, PerfTz); // PERF: += полный перенос сетки в битмап
PerfTz := PerfNow; // PERF
{$ENDIF} {$ENDIF}
RawBegin; RawBegin;
try try
{$IFNDEF DARWIN} {$IFNDEF DARWIN}
CopyGridToSpectrum(W, H); CopyGridToSpectrum(W, H);
PerfAdd(pzUiSpecGrid, PerfTz); // PERF: += полный memcpy сетки в битмап
PerfTz := PerfNow; // PERF
{$ENDIF} {$ENDIF}
// Полосы фильтра // Полосы фильтра
@@ -1465,15 +1453,11 @@ begin
// Полосы фильтра слайсов (B+) — под градиентом/кривой, как в GL-вьюхе. // Полосы фильтра слайсов (B+) — под градиентом/кривой, как в GL-вьюхе.
DrawSliceFilterBands(W, H); DrawSliceFilterBands(W, H);
PerfAdd(pzUiSpecBands, PerfTz); // PERF: полосы фильтра (raw-заливки)
PerfTz := PerfNow; // PERF
// Градиент (заливка под кривой) // Градиент (заливка под кривой)
if FFillSpectrum then if FFillSpectrum then
DrawSpectrumGradient(FSpPts, W, H); DrawSpectrumGradient(FSpPts, W, H);
PerfAdd(pzUiSpecCurve, PerfTz); // PERF: += градиент под кривой
PerfTz := PerfNow; // PERF
// Буква главного флага (A) на его полосе — только когда есть слайсы, // Буква главного флага (A) на его полосе — только когда есть слайсы,
// иначе одиночный приём не засоряем. // иначе одиночный приём не засоряем.
@@ -1532,8 +1516,6 @@ begin
if FMarkerActive then DrawMarkerLineRaw(W, H); if FMarkerActive then DrawMarkerLineRaw(W, H);
if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkersRaw(W, H); if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkersRaw(W, H);
PerfAdd(pzUiSpecOverlays, PerfTz); // PERF: raw-линии/текст/кривая/маркеры
PerfTz := PerfNow; // PERF
// Композиты оверлеев — внутри внешнего лока: их собственные BeginUpdate/ // Композиты оверлеев — внутри внешнего лока: их собственные BeginUpdate/
// EndUpdate становятся вложенными (только счётчик), иначе каждая пара на // EndUpdate становятся вложенными (только счётчик), иначе каждая пара на
@@ -1553,8 +1535,6 @@ begin
for i := 0 to FSliceOverlays.Count - 1 do for i := 0 to FSliceOverlays.Count - 1 do
TVfoOverlay(FSliceOverlays[i]).DrawOverlay(FSpectrumBitmap, W, H); TVfoOverlay(FSliceOverlays[i]).DrawOverlay(FSpectrumBitmap, W, H);
PerfAdd(pzUiComposites, PerfTz); // PERF: композиты бэндплан/панель/флаги
PerfTz := PerfNow; // PERF
finally finally
RawEnd; RawEnd;
end; end;
@@ -1582,8 +1562,6 @@ begin
{$ENDIF} {$ENDIF}
end; end;
PerfAdd(pzUiSpecAlpha, PerfTz); // PERF: RawEnd (EndUpdate) + редкий ADC-путь
PerfAdd(pzUiDrawSpectrum, PerfT0); // PERF: CPU-рендер кадра спектра
end; end;
// ──────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────────
@@ -1591,11 +1569,8 @@ end;
// ──────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────────
procedure TSpectrumView.DrawWaterfall; procedure TSpectrumView.DrawWaterfall;
var T0: Int64; // PERF
begin begin
T0 := PerfNow; // PERF
FWaterfall.DrawWaterfall; FWaterfall.DrawWaterfall;
PerfAdd(pzUiDrawWaterfall, T0); // PERF: CPU-рендер строк водопада
end; end;
procedure TSpectrumView.DrawRuler; procedure TSpectrumView.DrawRuler;
@@ -1610,20 +1585,14 @@ end;
procedure TSpectrumView.PaintSpectrum(Sender: TObject); procedure TSpectrumView.PaintSpectrum(Sender: TObject);
var var
PB: TPaintBox; PB: TPaintBox;
T0: Int64; // PERF
begin begin
T0 := PerfNow; // PERF
PB := TPaintBox(Sender); PB := TPaintBox(Sender);
PB.Canvas.Draw(0, 0, FSpectrumBitmap); PB.Canvas.Draw(0, 0, FSpectrumBitmap);
PerfAdd(pzUiPaintSpectrum, T0); // PERF: блит битмапа спектра (Qt6 canvas)
end; end;
procedure TSpectrumView.PaintWaterfall(Sender: TObject); procedure TSpectrumView.PaintWaterfall(Sender: TObject);
var T0: Int64; // PERF
begin begin
T0 := PerfNow; // PERF
FWaterfall.PaintWaterfall(Sender); FWaterfall.PaintWaterfall(Sender);
PerfAdd(pzUiPaintWaterfall, T0); // PERF: блит битмапа водопада (Qt6 canvas)
end; end;
procedure TSpectrumView.PaintRuler(Sender: TObject); procedure TSpectrumView.PaintRuler(Sender: TObject);
+1 -7
View File
@@ -17,8 +17,7 @@ interface
uses uses
Classes, SysUtils, Graphics, Controls, Math, Types, Classes, SysUtils, Graphics, Controls, Math, Types,
OpenGLContext, GL, OpenGLContext, GL,
AppTheme, SpectrumView, VfoOverlay, BandPlanOverlay, WaterfallViewOpengl, AppTheme, SpectrumView, VfoOverlay, BandPlanOverlay, WaterfallViewOpengl;
PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
type type
TGLTextureCache = record TGLTextureCache = record
@@ -937,12 +936,10 @@ procedure TSpectrumViewOpenGL.PaintSpectrum(Sender: TObject);
var var
W, H: Integer; W, H: Integer;
DBmax, DBmin, InvRange: Double; DBmax, DBmin, InvRange: Double;
T0, Tsw: Int64; // PERF
begin begin
if Sender is TOpenGLControl then if Sender is TOpenGLControl then
FGLControl := TOpenGLControl(Sender); FGLControl := TOpenGLControl(Sender);
if (FGLControl = nil) or (not FGLControl.MakeCurrent) then Exit; if (FGLControl = nil) or (not FGLControl.MakeCurrent) then Exit;
T0 := PerfNow; // PERF
W := FGLControl.Width; W := FGLControl.Width;
H := FGLControl.Height; H := FGLControl.Height;
if (W <= 0) or (H <= 0) then Exit; if (W <= 0) or (H <= 0) then Exit;
@@ -974,10 +971,7 @@ begin
DrawCachedOverlays(W, H); DrawCachedOverlays(W, H);
FLastADCVisible := FADCOverloadVisible; FLastADCVisible := FADCOverloadVisible;
Tsw := PerfNow; // PERF
FGLControl.SwapBuffers; FGLControl.SwapBuffers;
PerfAdd(pzUiGlSwap, Tsw); // PERF: SwapBuffers (в т.ч. ожидание vsync)
PerfAdd(pzUiPaintSpectrum, T0); // PERF: GL-кадр спектра (включая SwapBuffers)
end; end;
end. end.
-23
View File
@@ -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
@@ -1365,12 +1364,10 @@ procedure TWDSPEngine.PushIQItemToDSP(const Item: TIQQueueItem);
var var
i, Pos: Integer; i, Pos: Integer;
IR, QR: LongInt; IR, QR: LongInt;
T0: Int64; // PERF
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 не вызывается).
@@ -1404,11 +1401,9 @@ begin
if FRXAccPos >= FBufSize then if FRXAccPos >= FBufSize then
ProcessRXBlock; ProcessRXBlock;
end; end;
PerfAdd(pzDspPushIQ, T0); // PERF: весь путь пакета через DSP-поток
end; end;
procedure TWDSPEngine.FeedDisplaySample(const I, Q: Double); procedure TWDSPEngine.FeedDisplaySample(const I, Q: Double);
var T0: Int64; // PERF
begin begin
if not FAnalyzerOpen then Exit; if not FAnalyzerOpen then Exit;
@@ -1418,9 +1413,7 @@ begin
if FDispPos >= DISPLAY_BLOCK_SIZE then if FDispPos >= DISPLAY_BLOCK_SIZE then
begin begin
T0 := PerfNow; // PERF
Spectrum0(1, RX_DISP_ID, 0, 0, @FDispBuf[0]); Spectrum0(1, RX_DISP_ID, 0, 0, @FDispBuf[0]);
PerfAdd(pzDspSpectrum0, T0); // PERF: скармливание блока анализатору
FDispPos := 0; FDispPos := 0;
end; end;
end; end;
@@ -1444,14 +1437,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
@@ -1462,7 +1453,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 сбрасывает аккумулятор после вызова, поэтому второй
@@ -1484,14 +1474,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-каналы.
@@ -1598,10 +1586,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
@@ -1615,9 +1601,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;
@@ -1631,7 +1615,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;
@@ -2215,10 +2198,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);
@@ -2254,7 +2235,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);
@@ -2552,10 +2532,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
@@ -2578,7 +2556,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;
+1 -7
View File
@@ -16,8 +16,7 @@ interface
uses uses
Classes, SysUtils, Graphics, Controls, Math, Classes, SysUtils, Graphics, Controls, Math,
OpenGLContext, GL, OpenGLContext, GL,
AppTheme, WaterfallView, AppTheme, WaterfallView;
PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
type type
TWaterfallViewOpenGL = class(TWaterfallView) TWaterfallViewOpenGL = class(TWaterfallView)
@@ -462,11 +461,9 @@ procedure TWaterfallViewOpenGL.PaintWaterfall(Sender: TObject);
var var
W, H: Integer; W, H: Integer;
R, G, B: GLFloat; R, G, B: GLFloat;
T0, Tsw: Int64; // PERF
begin begin
if Sender is TOpenGLControl then FGLControl := TOpenGLControl(Sender); if Sender is TOpenGLControl then FGLControl := TOpenGLControl(Sender);
if (FGLControl = nil) or (not FGLControl.MakeCurrent) then Exit; if (FGLControl = nil) or (not FGLControl.MakeCurrent) then Exit;
T0 := PerfNow; // PERF
W := FGLControl.Width; W := FGLControl.Width;
H := FGLControl.Height; H := FGLControl.Height;
if (W <= 0) or (H <= 0) then Exit; if (W <= 0) or (H <= 0) then Exit;
@@ -494,10 +491,7 @@ begin
end; end;
DrawTextureWrapped(W, H); DrawTextureWrapped(W, H);
DrawMarker(W, H); DrawMarker(W, H);
Tsw := PerfNow; // PERF
FGLControl.SwapBuffers; FGLControl.SwapBuffers;
PerfAdd(pzUiGlSwap, Tsw); // PERF: SwapBuffers (в т.ч. ожидание vsync)
PerfAdd(pzUiPaintWaterfall, T0); // PERF: GL-кадр водопада (включая SwapBuffers)
end; end;
procedure TWaterfallViewOpenGL.SetTheme(const T: TAppTheme); procedure TWaterfallViewOpenGL.SetTheme(const T: TAppTheme);
+1 -5
View File
@@ -18,8 +18,7 @@ interface
uses uses
Classes, SysUtils, Math, Graphics, Controls, ExtCtrls, Classes, SysUtils, Math, Graphics, Controls, ExtCtrls,
OpenGLContext, GL, AppTheme, WDSP, OpenGLContext, GL, AppTheme, WDSP;
PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
type type
TWidebandView = class TWidebandView = class
@@ -1121,14 +1120,11 @@ begin
end; end;
procedure TWidebandView.Paint(Sender: TObject); procedure TWidebandView.Paint(Sender: TObject);
var T0: Int64; // PERF
begin begin
T0 := PerfNow; // PERF
if Sender is TOpenGLControl then if Sender is TOpenGLControl then
PaintGL(TOpenGLControl(Sender)) PaintGL(TOpenGLControl(Sender))
else if Sender is TPaintBox then else if Sender is TPaintBox then
PaintCPU(TPaintBox(Sender).Canvas); PaintCPU(TPaintBox(Sender).Canvas);
PerfAdd(pzUiWidebandPaint, T0); // PERF: кадр wideband-панорамы (CPU или GL)
end; end;
procedure TWidebandView.PaintRuler(Sender: TObject); procedure TWidebandView.PaintRuler(Sender: TObject);
-85
View File
@@ -1,85 +0,0 @@
# CPU-профилирование (ветка perf/cpu-profiling)
Временная инструментация для поиска самых дорогих по CPU мест.
Модуль `PerfLog.pas` + зонные замеры, помеченные комментарием `// PERF`.
**Не мержить в main** — после выводов ветка выбрасывается или чистится.
## Как запустить
```sh
lazbuild -B --ws=qt6 ewsdr.lpr
./bin/x86_64-linux/ewsdr-debug # отчёт в stderr каждые 5 с
EWSDR_PERF_FILE=/tmp/ewsdr-perf.log ./bin/x86_64-linux/ewsdr-debug # + в файл
EWSDR_PERF=0 ./bin/x86_64-linux/ewsdr-debug # выключить замеры
```
## Как читать отчёт
```
[PERF] ===== interval 5.00 s =====
[PERF] zone calls c/s avg_us max_us total_ms core%
[PERF] dsp.push_iq 2929 586 55.1 412 161.4 3.2
```
`core%` — доля одного ядра CPU, потраченная в зоне за интервал. Это главная
колонка: сортируем по ней и смотрим, куда уходит время. `max_us` показывает
выбросы (джиттер), `avg_us` — стоимость одного вызова.
Зоны вложены: `dsp.push_iq` включает `dsp.spectrum0` + `dsp.fex_main` +
`dsp.audio_cb` + `dsp.slices`; `dsp.slices` включает `dsp.slice_fex` и
`audio.write` слайсов; `ui.spectrum_tick` включает `ui.draw_*`, `ui.slice_flags`,
`ui.vfo_overlay`, `ui.web_push`; `dsp.tx_block` включает `pluto.tx_resamp`
(SendDUCIQ зовётся из FOnTXIQ). Разница «родитель минус дети» = ненаблюдаемый
остаток (циклы копирования, мелочь).
Бэкенды: на openHPSDR работает `net.*`, на Pluto вместо них `pluto.*`
(RX-поток Pluto конвертит iio-буфер и кладёт в ту же DSP-очередь).
## Карта зон
| Зона | Поток | Что меряет |
|---|---|---|
| net.ddc_iq | receive | копия IQ-пакета + постановка в DSP-очередь (openHPSDR) |
| net.wideband | receive | сборка wideband-кадра (openHPSDR) |
| pluto.rx_conv | Pluto RX | конверсия iio-буфера int16→24bit BE + отдача в DSP-очередь |
| pluto.tx_resamp | TX DSP | полифазный интерполятор 192k→device-rate + 24→16 + FIFO |
| pluto.tx_fill | Pluto TX | наполнение iio-буфера из FIFO (без блокирующего push) |
| dsp.push_iq | DSP | весь путь пакета: декод 24-бит, аккумуляция, всё ниже |
| dsp.spectrum0 | DSP | скармливание display-блока анализатору WDSP |
| dsp.fex_main | DSP | fexchange0 главного RX-канала |
| dsp.audio_cb | DSP | маршрут RX-аудио (web/радио/звуковуха) |
| dsp.slices | DSP | fan-out всех слайсов целиком |
| dsp.slice_fex | DSP | fexchange0 одного слайс-канала |
| dsp.tx_block | TX DSP | TX-блок целиком |
| disp.get_pixels | display | GetPixels спектр+водопад |
| audio.write | DSP | запись в PortAudio ring (главный + все слайсы) |
| ui.spectrum_tick | UI | кадровый таймер целиком |
| ui.meter_tick | UI | 10 Гц метр-таймер |
| ui.draw_spectrum / ui.draw_waterfall | UI | CPU-рендер кадра в битмап |
| ui.paint_spectrum / ui.paint_waterfall | UI | блит на канву (Qt6) или GL-кадр со SwapBuffers |
| ui.slice_flags | UI | позиционирование + S-метры слайс-флагов |
| ui.vfo_overlay | UI | обновление главного VFO-флага |
| ui.wideband_paint | UI | кадр wideband-панорамы |
| ui.web_push | UI | WebAdapter.PushState |
| ui.spec_grid…spec_alpha | UI | разбивка ui.draw_spectrum (full-raw рендер): grid=ребилд+memcpy сетки, bands=raw-заливки полос, curve=точки+градиент, overlays=raw-линии/текст/кривая/маркеры, composites=бэндплан/панель/флаги, alpha=RawEnd/EndUpdate (+редкий canvas-путь ADC overload) |
| ui.gl_swap | UI | SwapBuffers отдельно — включает ожидание vsync, НЕ чистый CPU |
## Чего зоны НЕ видят
- Общесистемное: Qt event loop, композитор, PortAudio callback-поток
(сам колбэк не инструментирован — он читает ring без работы).
- Для сравнения с общим CPU процесса: `pidstat -p $(pidof ewsdr-debug) 5 -t`
(покажет и раскладку по потокам — сопоставляется с зонами по потокам).
## Методика измерений
Снять отчёты в фиксированных сценариях по ~30 с каждый и сравнить core%:
1. RX, 1 слайс (только VFO A), спектр CPU-рендер.
2. То же + OpenGL (`ewsdr-debug --opengl` или как включается GL-свитч).
3. RX + 34 слайса (Ctrl+ЛКМ) — рост dsp.slices / dsp.slice_fex / audio.write.
4. Разные FPS дисплея и размеры FFT.
5. Открытый web-клиент (ui.web_push, dsp.audio_cb с Opus).
6. Wideband включён/выключен (openHPSDR).
7. Pluto: разные sample rate (576k/1536k/3072k) — pluto.rx_conv и dsp.push_iq
растут линейно с рейтом; TX (MOX/TUN) — pluto.tx_resamp vs dsp.tx_block.
+3 -2
View File
@@ -17,9 +17,9 @@
<UseVersionInfo Value="True"/> <UseVersionInfo Value="True"/>
<AutoIncrementBuild Value="True"/> <AutoIncrementBuild Value="True"/>
<MinorVersionNr Value="9"/> <MinorVersionNr Value="9"/>
<BuildNr Value="244"/> <BuildNr Value="245"/>
</VersionInfo> </VersionInfo>
<MacroValues Count="62"> <MacroValues Count="63">
<Macro1 Name="LCLWidgetType" Value="qt6"/> <Macro1 Name="LCLWidgetType" Value="qt6"/>
<Macro2 Name="LCLWidgetType" Value="qt6"/> <Macro2 Name="LCLWidgetType" Value="qt6"/>
<Macro3 Name="LCLWidgetType" Value="qt6"/> <Macro3 Name="LCLWidgetType" Value="qt6"/>
@@ -82,6 +82,7 @@
<Macro60 Name="LCLWidgetType" Value="qt6"/> <Macro60 Name="LCLWidgetType" Value="qt6"/>
<Macro61 Name="LCLWidgetType" Value="qt6"/> <Macro61 Name="LCLWidgetType" Value="qt6"/>
<Macro62 Name="LCLWidgetType" Value="qt6"/> <Macro62 Name="LCLWidgetType" Value="qt6"/>
<Macro63 Name="LCLWidgetType" Value="qt6"/>
</MacroValues> </MacroValues>
<BuildModes> <BuildModes>
<Item Name="Debug" Default="True"/> <Item Name="Debug" Default="True"/>