diff --git a/AudioOutput.pas b/AudioOutput.pas
index bc4b7f8..b6b9df2 100644
--- a/AudioOutput.pas
+++ b/AudioOutput.pas
@@ -21,8 +21,7 @@ unit AudioOutput;
interface
uses
- Classes, SysUtils, Math, SyncObjs, DynLibs,
- PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
+ Classes, SysUtils, Math, SyncObjs, DynLibs;
const
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)
var
i: Integer;
- T0: Int64; // PERF
begin
if not FOpen then Exit;
- T0 := PerfNow; // PERF
for i := 0 to Count - 1 do
WriteDouble(Left[i], Right[i]);
- PerfAdd(pzAudioWrite, T0); // PERF: запись блока в PA ring (все экземпляры)
end;
end.
diff --git a/HPSDRNetwork.pas b/HPSDRNetwork.pas
index 193b593..d5114b8 100644
--- a/HPSDRNetwork.pas
+++ b/HPSDRNetwork.pas
@@ -19,8 +19,7 @@ uses
{$ELSE}
Sockets, BaseUnix,
{$ENDIF}
- HPSDRProtocol, SyncObjs, Settings, RadioBackend,
- PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
+ HPSDRProtocol, SyncObjs, Settings, RadioBackend;
const
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);
var
Pkt: TDDCIQPacket;
- T0: Int64; // PERF
begin
if not Assigned(FOnDDCIQ) then Exit;
- T0 := PerfNow; // PERF
FillChar(Pkt, SizeOf(Pkt), 0);
Move(Buf[0], Pkt, Min(Len, SizeOf(Pkt)));
FOnDDCIQ(DDCIdx, Pkt); // прямой вызов из потока — UI не трогает
- PerfAdd(pzNetDDCIQ, T0); // PERF: копия пакета + постановка в DSP-очередь
end;
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
Seq: LongWord;
I, SamplesInPacket, TargetCount, S: Integer;
- T0: Int64; // PERF
begin
if (not FWidebandEnabled) or (ADCIdx <> FWidebandADC) or
(FWBSampleBits <> 16) or (Len < 6) then Exit;
- T0 := PerfNow; // PERF
SamplesInPacket := (Len - 4) div 2;
if SamplesInPacket <= 0 then Exit;
@@ -1163,7 +1157,6 @@ begin
FWBFrameCount := 0;
FWBSeqValid := False;
end;
- PerfAdd(pzNetWideband, T0); // PERF: сборка wideband-кадра + колбэк
end;
// ---------------------------------------------------------------------------
diff --git a/MainForm.pas b/MainForm.pas
index c9799a2..5dfc88a 100644
--- a/MainForm.pas
+++ b/MainForm.pas
@@ -37,7 +37,6 @@ uses
WidebandView,
StatusBar,
PlatformUtils,
- PerfLog, // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
WinFirewall,
BoardUtils, WisdomBuilder, UISync,
FlatEdit, FMRepeater,
@@ -3169,10 +3168,7 @@ var
PeakTarget, MinTarget: Double;
PeakDiff, MinDiff: Double;
PeakAlpha, MinAlpha: Double;
- PerfT0: Int64; // PERF
begin
- PerfT0 := PerfNow; // PERF
- try
if not FController.FRunning then
begin
if PbSMeterRight <> nil then PbSMeterRight.Invalidate;
@@ -3226,9 +3222,6 @@ begin
else
SetStatusText(5, 'TX idle');
end;
- finally
- PerfAdd(pzUiMeterTick, PerfT0); // PERF: тик метра целиком (10 Гц)
- end;
end;
procedure TMainForm.SpectrumTimerTick(Sender: TObject);
@@ -3236,10 +3229,7 @@ var
NowTick: QWord;
ElapsedTick: QWord;
I: Integer;
- PerfT0, PerfTz: Int64; // PERF
begin
- PerfT0 := PerfNow; // PERF
- try
// Синхронизируем размеры если размер панели изменился (после ресайза).
// Важно: проверяем только видимые панели — когда спектр скрыт,
// PbSpectrum.Height может быть ненулевым при FSpectrumHeight=0,
@@ -3312,16 +3302,13 @@ begin
// Обновляем оверлей если видим
if Assigned(FVfoOverlay) and FVfoOverlay.Visible then
begin
- PerfTz := PerfNow; // PERF
FVfoOverlay.UpdateSMeter(FController.FLastSMeter);
FVfoOverlay.UpdateVfo(FController.FVfoA);
PushVfoOverlayExtState;
PositionVfoOverlay;
- PerfAdd(pzUiVfoOverlay, PerfTz); // PERF: обновление главного VFO-флага
end;
if Length(FSliceFlags) > 0 then
begin
- PerfTz := PerfNow; // PERF
PositionSliceFlags; // держим слайс-флаги на частоте
// S-метр слайсов троттлим до ~10 Гц: каждое изменение метра = софтовый
// ре-рендер флага (дорого), 60 Гц там не нужно.
@@ -3332,7 +3319,6 @@ begin
if FSliceFlags[I] <> nil then
FSliceFlags[I].UpdateSMeter(FController.SliceSMeter(FSliceFlags[I].SliceId));
end;
- PerfAdd(pzUiSliceFlags, PerfTz); // PERF: позиционирование + S-метры слайс-флагов
end;
// Панорама и водопад теперь рисуются независимо по факту новых кадров.
// Это лучше совпадает с Thetis: маленький FFT даёт новые строки чаще.
@@ -3399,14 +3385,7 @@ begin
// Исходящее зеркало состояния + спектр/водопад в web — в адаптере (всё из
// FController, включая последний кадр пикселей). Сервер сам гейтит по FRunning.
if Assigned(FWebAdapter) then
- begin
- PerfTz := PerfNow; // PERF
FWebAdapter.PushState;
- PerfAdd(pzUiWebPush, PerfTz); // PERF: web-зеркало состояния + кадры клиентам
- end;
- finally
- PerfAdd(pzUiSpectrumTick, PerfT0); // PERF: тик спектра целиком (кадровый таймер)
- end;
end;
// ===========================================================================
diff --git a/PerfLog.pas b/PerfLog.pas
deleted file mode 100644
index eb0278a..0000000
--- a/PerfLog.pas
+++ /dev/null
@@ -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.
diff --git a/PlutoBackend.pas b/PlutoBackend.pas
index 4c7255c..6222a22 100644
--- a/PlutoBackend.pas
+++ b/PlutoBackend.pas
@@ -26,8 +26,7 @@ interface
uses
Classes, SysUtils, ctypes,
- RadioBackend, IIOBindings, HPSDRProtocol, Settings,
- PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
+ RadioBackend, IIOBindings, HPSDRProtocol, Settings;
type
{ TPlutoBackend }
@@ -196,7 +195,6 @@ var
outc: Integer;
vi, vq: LongInt;
u: LongWord;
- T0: Int64; // PERF
procedure FlushPkt;
begin
@@ -222,7 +220,6 @@ begin
if Terminated then Break;
if n <= 0 then begin Sleep(2); Continue; end;
- T0 := PerfNow; // PERF: только конверсия, без блокирующего refill
step := iio_buffer_step(FBk.FRxBuf);
pFirst := iio_buffer_first(FBk.FRxBuf, FBk.FRxChI);
pEnd := iio_buffer_end(FBk.FRxBuf);
@@ -249,7 +246,6 @@ begin
Inc(p, step);
end;
if outc > 0 then FlushPkt; // хвост блока
- PerfAdd(pzPlutoRxConv, T0); // PERF: int16→24bit BE + FOnDDCIQ на весь iio-буфер
end;
end;
@@ -266,12 +262,10 @@ var
pFirst, pEnd, p: PByte;
h: Integer;
n: ptrint;
- T0: Int64; // PERF
begin
while not Terminated do
begin
if FBk.FTxBuf = nil then begin Sleep(5); Continue; end;
- T0 := PerfNow; // PERF
step := iio_buffer_step(FBk.FTxBuf);
pFirst := iio_buffer_first(FBk.FTxBuf, FBk.FTxChI);
pEnd := iio_buffer_end(FBk.FTxBuf);
@@ -297,7 +291,6 @@ begin
Inc(p, step);
end;
LeaveCriticalSection(FBk.FTxLock);
- PerfAdd(pzPlutoTxFill, T0); // PERF: наполнение буфера (без блокирующего push)
if Terminated then Break;
n := iio_buffer_push(FBk.FTxBuf); // блокирует → пейсинг на device-rate
@@ -860,10 +853,8 @@ procedure TPlutoBackend.SendDUCIQ(const IData, QData: array of Integer);
var
i, p, k, t, L, taps, base, n: Integer;
ai, aq: Double;
- T0: Int64; // PERF
begin
if (not FTxStreaming) or (FTxRatio < 1) or (Length(FTxCoef) = 0) then Exit;
- T0 := PerfNow; // PERF
n := Length(IData);
if Length(QData) < n then n := Length(QData);
L := FTxRatio;
@@ -902,7 +893,6 @@ begin
finally
LeaveCriticalSection(FTxLock);
end;
- PerfAdd(pzPlutoTxResamp, T0); // PERF: полифазный интерполятор + 24→16 + FIFO
end;
procedure TPlutoBackend.ClearDUCIQQueue;
diff --git a/SpectrumView.pas b/SpectrumView.pas
index 41c35ea..4f09e16 100644
--- a/SpectrumView.pas
+++ b/SpectrumView.pas
@@ -21,8 +21,7 @@ uses
Classes, SysUtils, Graphics, GraphType, ExtCtrls, Controls, Math,
AppTheme,
AlertOverlay, SampleRateOverlay, VfoOverlay, BandPlanOverlay,
- WaterfallView, SMeterView, RulerView,
- PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
+ WaterfallView, SMeterView, RulerView;
const
SV_CLR_BG = TColor($00101010);
@@ -1280,11 +1279,8 @@ var
LabelBandW, SrcCount: Integer;
GridFreqS, GridLine, pixPerStep: Double;
N, gridMult: Integer;
- PerfT0, PerfTz: Int64; // PERF
begin
if FSpectrumBitmap = nil then Exit;
- PerfT0 := PerfNow; // PERF
- PerfTz := PerfT0; // PERF
W := FSpectrumBitmap.Width; H := FSpectrumBitmap.Height;
if (W <= 0) or (H <= 0) then Exit;
C := FSpectrumBitmap.Canvas;
@@ -1377,8 +1373,6 @@ begin
FGridBitmap.EndUpdate(False);
{$ENDIF}
end;
- PerfAdd(pzUiSpecGrid, PerfTz); // PERF: ребилд кэша сетки (обычно 0)
- PerfTz := PerfNow; // PERF
// ═══ Фаза 0: чистые вычисления — битмап не трогаем ═══════════════════════
// Дальше битмап обрабатывается тремя непрерывными фазами: raw (ScanLine) →
@@ -1431,23 +1425,17 @@ begin
end;
FSpPts[W] := Point(W-1, H); FSpPts[W+1] := Point(0, H);
- PerfAdd(pzUiSpecCurve, PerfTz); // PERF: расчёт точек кривой
- PerfTz := PerfNow; // PERF
// ═══ Фаза 1: raw — весь кадр одним локом ═════════════════════════════════
{$IFDEF DARWIN}
// Cocoa-вариант CopyGridToSpectrum идёт через Canvas.Draw — до RawBegin.
CopyGridToSpectrum(W, H);
- PerfAdd(pzUiSpecGrid, PerfTz); // PERF: += полный перенос сетки в битмап
- PerfTz := PerfNow; // PERF
{$ENDIF}
RawBegin;
try
{$IFNDEF DARWIN}
CopyGridToSpectrum(W, H);
- PerfAdd(pzUiSpecGrid, PerfTz); // PERF: += полный memcpy сетки в битмап
- PerfTz := PerfNow; // PERF
{$ENDIF}
// Полосы фильтра
@@ -1465,15 +1453,11 @@ begin
// Полосы фильтра слайсов (B+) — под градиентом/кривой, как в GL-вьюхе.
DrawSliceFilterBands(W, H);
- PerfAdd(pzUiSpecBands, PerfTz); // PERF: полосы фильтра (raw-заливки)
- PerfTz := PerfNow; // PERF
// Градиент (заливка под кривой)
if FFillSpectrum then
DrawSpectrumGradient(FSpPts, W, H);
- PerfAdd(pzUiSpecCurve, PerfTz); // PERF: += градиент под кривой
- PerfTz := PerfNow; // PERF
// Буква главного флага (A) на его полосе — только когда есть слайсы,
// иначе одиночный приём не засоряем.
@@ -1532,8 +1516,6 @@ begin
if FMarkerActive then DrawMarkerLineRaw(W, H);
if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkersRaw(W, H);
- PerfAdd(pzUiSpecOverlays, PerfTz); // PERF: raw-линии/текст/кривая/маркеры
- PerfTz := PerfNow; // PERF
// Композиты оверлеев — внутри внешнего лока: их собственные BeginUpdate/
// EndUpdate становятся вложенными (только счётчик), иначе каждая пара на
@@ -1553,8 +1535,6 @@ begin
for i := 0 to FSliceOverlays.Count - 1 do
TVfoOverlay(FSliceOverlays[i]).DrawOverlay(FSpectrumBitmap, W, H);
- PerfAdd(pzUiComposites, PerfTz); // PERF: композиты бэндплан/панель/флаги
- PerfTz := PerfNow; // PERF
finally
RawEnd;
end;
@@ -1582,8 +1562,6 @@ begin
{$ENDIF}
end;
- PerfAdd(pzUiSpecAlpha, PerfTz); // PERF: RawEnd (EndUpdate) + редкий ADC-путь
- PerfAdd(pzUiDrawSpectrum, PerfT0); // PERF: CPU-рендер кадра спектра
end;
// ────────────────────────────────────────────────────────────────────────────
@@ -1591,11 +1569,8 @@ end;
// ────────────────────────────────────────────────────────────────────────────
procedure TSpectrumView.DrawWaterfall;
-var T0: Int64; // PERF
begin
- T0 := PerfNow; // PERF
FWaterfall.DrawWaterfall;
- PerfAdd(pzUiDrawWaterfall, T0); // PERF: CPU-рендер строк водопада
end;
procedure TSpectrumView.DrawRuler;
@@ -1610,20 +1585,14 @@ end;
procedure TSpectrumView.PaintSpectrum(Sender: TObject);
var
PB: TPaintBox;
- T0: Int64; // PERF
begin
- T0 := PerfNow; // PERF
PB := TPaintBox(Sender);
PB.Canvas.Draw(0, 0, FSpectrumBitmap);
- PerfAdd(pzUiPaintSpectrum, T0); // PERF: блит битмапа спектра (Qt6 canvas)
end;
procedure TSpectrumView.PaintWaterfall(Sender: TObject);
-var T0: Int64; // PERF
begin
- T0 := PerfNow; // PERF
FWaterfall.PaintWaterfall(Sender);
- PerfAdd(pzUiPaintWaterfall, T0); // PERF: блит битмапа водопада (Qt6 canvas)
end;
procedure TSpectrumView.PaintRuler(Sender: TObject);
diff --git a/SpectrumViewOpengl.pas b/SpectrumViewOpengl.pas
index dc4f18a..a432df5 100644
--- a/SpectrumViewOpengl.pas
+++ b/SpectrumViewOpengl.pas
@@ -17,8 +17,7 @@ interface
uses
Classes, SysUtils, Graphics, Controls, Math, Types,
OpenGLContext, GL,
- AppTheme, SpectrumView, VfoOverlay, BandPlanOverlay, WaterfallViewOpengl,
- PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
+ AppTheme, SpectrumView, VfoOverlay, BandPlanOverlay, WaterfallViewOpengl;
type
TGLTextureCache = record
@@ -937,12 +936,10 @@ procedure TSpectrumViewOpenGL.PaintSpectrum(Sender: TObject);
var
W, H: Integer;
DBmax, DBmin, InvRange: Double;
- T0, Tsw: Int64; // PERF
begin
if Sender is TOpenGLControl then
FGLControl := TOpenGLControl(Sender);
if (FGLControl = nil) or (not FGLControl.MakeCurrent) then Exit;
- T0 := PerfNow; // PERF
W := FGLControl.Width;
H := FGLControl.Height;
if (W <= 0) or (H <= 0) then Exit;
@@ -974,10 +971,7 @@ begin
DrawCachedOverlays(W, H);
FLastADCVisible := FADCOverloadVisible;
- Tsw := PerfNow; // PERF
FGLControl.SwapBuffers;
- PerfAdd(pzUiGlSwap, Tsw); // PERF: SwapBuffers (в т.ч. ожидание vsync)
- PerfAdd(pzUiPaintSpectrum, T0); // PERF: GL-кадр спектра (включая SwapBuffers)
end;
end.
diff --git a/WDSPEngine.pas b/WDSPEngine.pas
index 0daac90..52aebca 100644
--- a/WDSPEngine.pas
+++ b/WDSPEngine.pas
@@ -26,7 +26,6 @@ interface
uses
Classes, SysUtils, Math, SyncObjs,
BeaconDecoder,
- PerfLog, // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
WDSP;
const
@@ -1365,12 +1364,10 @@ procedure TWDSPEngine.PushIQItemToDSP(const Item: TIQQueueItem);
var
i, Pos: Integer;
IR, QR: LongInt;
- T0: Int64; // PERF
const
SCALE = 1.0 / 8388608.0;
begin
if FTXActive and not FKeepRXDuringTX then Exit;
- T0 := PerfNow; // PERF
// Post-TX hold: дропаем входящие RX-IQ пакеты пока активно окно мьюта.
// Это закрывает ОБА канала сразу — и водопад/спектр (анализатор не
// кормится), и аудио (RXA не запускается, FOnAudio не вызывается).
@@ -1404,11 +1401,9 @@ begin
if FRXAccPos >= FBufSize then
ProcessRXBlock;
end;
- PerfAdd(pzDspPushIQ, T0); // PERF: весь путь пакета через DSP-поток
end;
procedure TWDSPEngine.FeedDisplaySample(const I, Q: Double);
-var T0: Int64; // PERF
begin
if not FAnalyzerOpen then Exit;
@@ -1418,9 +1413,7 @@ begin
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;
@@ -1444,14 +1437,12 @@ procedure TWDSPEngine.ProcessRXBlock;
var
i: Integer;
Err: Integer;
- T0: Int64; // PERF
// OutL/OutR теперь поля класса — не аллоцируем каждый раз
begin
FRXAccPos := 0;
if not FInitialized then Exit;
try
- T0 := PerfNow; // PERF
// Упаковываем в interleaved буфер для fexchange0
for i := 0 to FBufSize - 1 do
begin
@@ -1462,7 +1453,6 @@ begin
// DSP обработка
Err := 0;
fexchange0(RXA_CHAN, @FRXIn[0], @FRXOut[0], @Err);
- PerfAdd(pzDspFexMain, T0); // PERF: главный RX-канал WDSP
// S-meter: читаем ОДИН раз в DSP-колбэке и кешируем в FSMeter.
// GetRXAMeter сбрасывает аккумулятор после вызова, поэтому второй
@@ -1484,14 +1474,12 @@ begin
and (not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX))
and (FPostTXMuteUntil = 0) then
begin
- T0 := PerfNow; // PERF
for i := 0 to FAudioBufSize - 1 do
begin
FOutL[i] := FRXOut[i * 2] * FVolume;
FOutR[i] := FRXOut[i * 2 + 1] * FVolume;
end;
FOnAudio(FOutL, FOutR, FAudioBufSize);
- PerfAdd(pzDspAudioCb, T0); // PERF: маршрут RX-аудио (web/радио/звуковуха)
end;
// Софтовые слайсы: fan-out того же IQ-блока на доп. WDSP-каналы.
@@ -1598,10 +1586,8 @@ procedure TWDSPEngine.ProcessSlices;
var
s, i, k, Err: Integer;
V: Double;
- T0, TS: Int64; // PERF
begin
if not Assigned(FOnSliceAudio) then Exit;
- T0 := PerfNow; // PERF
FSliceLock.Enter;
try
for s := 0 to MAX_SLICES - 1 do
@@ -1615,9 +1601,7 @@ begin
FSliceIn[i * 2 + 1] := FRXAccQ[i];
end;
Err := 0;
- TS := PerfNow; // PERF
fexchange0(FSlices[s].Chan, @FSliceIn[0], @FSliceOut[0], @Err);
- PerfAdd(pzDspSliceFex, TS); // PERF: fexchange0 одного слайса
// S-метр канала читаем здесь (DSP-поток) — GetRXAMeter сбрасывает аккумулятор.
FSlices[s].SMeter := GetRXAMeter(FSlices[s].Chan, RXA_S_AV);
V := FSlices[s].Volume;
@@ -1631,7 +1615,6 @@ begin
finally
FSliceLock.Leave;
end;
- PerfAdd(pzDspSlices, T0); // PERF: fan-out всех слайсов целиком
end;
function TWDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi: Integer;
@@ -2215,10 +2198,8 @@ var
i, tail: Integer;
Err: Integer;
Avail: Integer;
- T0: Int64; // PERF
begin
if not FInitialized then Exit;
- T0 := PerfNow; // PERF
Avail := (FTXMicHead - FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1);
@@ -2254,7 +2235,6 @@ begin
// FTXOut содержит FTXOutBufSize IQ пар @ FTXSampleRate (interleaved double)
if Assigned(FOnTXIQ) then
FOnTXIQ(FTXOut, FTXOutBufSize);
- PerfAdd(pzDspTxBlock, T0); // PERF: TX-блок целиком (fexchange0 + analyzer + IQ out)
end;
procedure TWDSPEngine.PushTXMicSamples16(const Src: array of SmallInt; N: Integer);
@@ -2552,10 +2532,8 @@ var
Flag: Integer;
i, N: Integer;
DispID: Integer;
- T0: Int64; // PERF
begin
if not FInitialized or not FAnalyzerOpen then Exit;
- T0 := PerfNow; // PERF
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
DispID := FActiveDisplayID; // RX_DISP_ID или TX_DISP_ID, зависит от FTXActive
@@ -2578,7 +2556,6 @@ begin
if Assigned(FOnWaterfall) then
FOnWaterfall(FWaterfallPixels, N);
end;
- PerfAdd(pzDispGetPixels, T0); // PERF: GetPixels + колбэки спектр/водопад
end;
procedure TWDSPEngine.GetSpectrumData(var Pixels: array of Single;
diff --git a/WaterfallViewOpengl.pas b/WaterfallViewOpengl.pas
index a5c9967..00a2a4d 100644
--- a/WaterfallViewOpengl.pas
+++ b/WaterfallViewOpengl.pas
@@ -16,8 +16,7 @@ interface
uses
Classes, SysUtils, Graphics, Controls, Math,
OpenGLContext, GL,
- AppTheme, WaterfallView,
- PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
+ AppTheme, WaterfallView;
type
TWaterfallViewOpenGL = class(TWaterfallView)
@@ -462,11 +461,9 @@ procedure TWaterfallViewOpenGL.PaintWaterfall(Sender: TObject);
var
W, H: Integer;
R, G, B: GLFloat;
- T0, Tsw: Int64; // PERF
begin
if Sender is TOpenGLControl then FGLControl := TOpenGLControl(Sender);
if (FGLControl = nil) or (not FGLControl.MakeCurrent) then Exit;
- T0 := PerfNow; // PERF
W := FGLControl.Width;
H := FGLControl.Height;
if (W <= 0) or (H <= 0) then Exit;
@@ -494,10 +491,7 @@ begin
end;
DrawTextureWrapped(W, H);
DrawMarker(W, H);
- Tsw := PerfNow; // PERF
FGLControl.SwapBuffers;
- PerfAdd(pzUiGlSwap, Tsw); // PERF: SwapBuffers (в т.ч. ожидание vsync)
- PerfAdd(pzUiPaintWaterfall, T0); // PERF: GL-кадр водопада (включая SwapBuffers)
end;
procedure TWaterfallViewOpenGL.SetTheme(const T: TAppTheme);
diff --git a/WidebandView.pas b/WidebandView.pas
index 460fe64..3ddf318 100644
--- a/WidebandView.pas
+++ b/WidebandView.pas
@@ -18,8 +18,7 @@ interface
uses
Classes, SysUtils, Math, Graphics, Controls, ExtCtrls,
- OpenGLContext, GL, AppTheme, WDSP,
- PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
+ OpenGLContext, GL, AppTheme, WDSP;
type
TWidebandView = class
@@ -1121,14 +1120,11 @@ begin
end;
procedure TWidebandView.Paint(Sender: TObject);
-var T0: Int64; // PERF
begin
- T0 := PerfNow; // PERF
if Sender is TOpenGLControl then
PaintGL(TOpenGLControl(Sender))
else if Sender is TPaintBox then
PaintCPU(TPaintBox(Sender).Canvas);
- PerfAdd(pzUiWidebandPaint, T0); // PERF: кадр wideband-панорамы (CPU или GL)
end;
procedure TWidebandView.PaintRuler(Sender: TObject);
diff --git a/doc/PERF_PROFILING.md b/doc/PERF_PROFILING.md
deleted file mode 100644
index b58065a..0000000
--- a/doc/PERF_PROFILING.md
+++ /dev/null
@@ -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 + 3–4 слайса (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.
diff --git a/ewsdr.lpi b/ewsdr.lpi
index 0b17dae..1e17ac1 100644
--- a/ewsdr.lpi
+++ b/ewsdr.lpi
@@ -17,9 +17,9 @@
-
+
-
+
@@ -82,6 +82,7 @@
+