Files
ewsdr/doc/PERF_PROFILING.md
T
ew8bakandClaude Fable 5 0adaff2d21 perf(spectrum): full-raw CPU renderer + dword blenders (frame 8.3ms -> 1.7ms)
Canvas на битмапе кадра больше не используется вообще (кроме редкого
ADC-overload пути с восстановлением альфы) — убраны все raw<->canvas
переходы, каждый из которых синкал весь битмап на Qt6 (~2мс):

- RawBegin/RawEnd: прямой доступ к пикселям через RawImage.Data +
  BytesPerLine (bottom-up DIB win32 учтён отрицательным шагом)
- RawVLine/RawHLine (с пунктиром), RawFillRect, RawTriangleDown,
  RawCurve (кривая = связные вертикальные сегменты по колонкам)
- кэш текстовых масок (TRawTextMask, LRU 48): строка рендерится canvas'ом
  один раз белым-на-чёрном в свой мини-битмап, блитится raw с цветом;
  все тексты кадра теперь Courier New
- конвертированы: маркер (метка без случайной цветной плашки), бикон-
  маркеры, буквы слайсов, слайс-линии, AGC (пунктир 4/2 и 1/2, подпись
  с фоном SpecFilter как раньше), края/VFO/TX-курсоры с треугольниками
- пофреймовый альфа-OR проход удалён: альфа чинится один раз в кэше
  СЕТКИ после ребилда (canvas-текст там), кадр наследует её через memcpy

Дворд-блендеры (два 16-бит лейна на умножение, /256 вместо /255,
расхождение <0.4%) вместо побайтовых с div 255:
- BandPlanOverlay.CompositeStrip (~1мс -> ~0.2мс)
- SampleRateOverlay.CompositeCache (per-px альфа)
- VfoOverlay.BlendBitmapKey (keyed-магента дворд-сравнением)
- SpectrumView.DrawSpectrumGradient (source-лейны 1 раз на строку,
  ~1.6мс -> ~0.8мс)

Порог перестройки бэндплана: полпикселя вместо 1 Гц (на QO-100 с
decoder-lock центр подтюнивается непрерывно — был canvas-ребилд полоски
каждый кадр). Композиты внутри raw-лока (их BeginUpdate вложенные).
PerfLog: зона ui.composites отделена от ui.spec_alpha (=RawEnd).

Замер на эфире (Pluto, 576k, 52fps): кадр 8.3мс -> 1.67мс (5x),
со слайсом 2.1-2.3мс; UI-поток ~45% -> ~26% ядра, из них 15% — блиты
paint_spectrum/paint_waterfall (потолок Qt6-вывода). Визуально проверено.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 00:11:31 +03:00

5.7 KiB
Raw Blame History

CPU-профилирование (ветка perf/cpu-profiling)

Временная инструментация для поиска самых дорогих по CPU мест. Модуль PerfLog.pas + зонные замеры, помеченные комментарием // PERF. Не мержить в main — после выводов ветка выбрасывается или чистится.

Как запустить

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.