perf: zone CPU profiler (PerfLog) + phase-restructured CPU spectrum render

Инструментация (ВРЕМЕННАЯ, не мержить в main):
- PerfLog.pas: зонные таймеры, отчёт раз в 5с в stderr (core% = доля ядра),
  EWSDR_PERF=0 выкл, EWSDR_PERF_FILE=путь — дублировать в файл
- Замеры // PERF: сеть (DDC IQ/wideband), Pluto (rx_conv/tx_resamp/tx_fill),
  DSP (push_iq/fexchange/slices/tx), аудио, UI (тики/draw/paint/gl_swap,
  разбивка draw_spectrum по фазам)
- doc/PERF_PROFILING.md: карта зон, методика, выводы

Оптимизация CPU-рендера спектра (по результатам замеров на эфире):
диагноз — дорога не пиксельная работа, а чередование raw(ScanLine)/
canvas(QPainter) доступа к битмапу на Qt6 (~6 переходов за кадр, каждый
синкает весь битмап; полоса фильтра в тысячи пикселей стоила 4x дороже
полного memcpy кадра). DrawSpectrum перестроен в фазы calc->raw->canvas->raw
(2 перехода): FillBandRaw вместо Canvas.FillRect, DrawSliceFilterMarkers
разделён на raw-полосы + canvas-линии, композиты флагов и альфа-OR в
финальной raw-фазе. Кадр 8.3мс -> 6.1мс; spec_bands 1.87мс -> 0.03мс.
Альфа-OR проход на Qt6 подтверждён нужным (без него canvas-пиксели теряют
альфу и пропадают при блите), сам цикл ~0.08мс — оставлен; тумблер
EWSDR_ALPHA_FIX=0 только для экспериментов. Кисть перед AGC-подписями
теперь ставится явно (была остаточной после перестройки фаз).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 22:07:36 +03:00
co-authored by Claude Fable 5
parent b2fa2756cf
commit 0b4d126dac
12 changed files with 692 additions and 83 deletions
+11 -1
View File
@@ -26,7 +26,8 @@ interface
uses
Classes, SysUtils, ctypes,
RadioBackend, IIOBindings, HPSDRProtocol, Settings;
RadioBackend, IIOBindings, HPSDRProtocol, Settings,
PerfLog; // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
type
{ TPlutoBackend }
@@ -195,6 +196,7 @@ var
outc: Integer;
vi, vq: LongInt;
u: LongWord;
T0: Int64; // PERF
procedure FlushPkt;
begin
@@ -220,6 +222,7 @@ 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);
@@ -246,6 +249,7 @@ begin
Inc(p, step);
end;
if outc > 0 then FlushPkt; // хвост блока
PerfAdd(pzPlutoRxConv, T0); // PERF: int16→24bit BE + FOnDDCIQ на весь iio-буфер
end;
end;
@@ -262,10 +266,12 @@ 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);
@@ -291,6 +297,7 @@ 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
@@ -853,8 +860,10 @@ 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;
@@ -893,6 +902,7 @@ begin
finally
LeaveCriticalSection(FTxLock);
end;
PerfAdd(pzPlutoTxResamp, T0); // PERF: полифазный интерполятор + 24→16 + FIFO
end;
procedure TPlutoBackend.ClearDUCIQQueue;