Files
Pluto-SDR/docs/BENCHMARK.md
Maxim fc836f2af1 Этап 2
- README §12.8: таблица gate (PER0 3/5/14, PER2 1-11 при FBGAIN -80..0,
  доставка STATUS 0% при <=-40, плато ~50% при -20..0), вердикт
- NOTES §2.2: разбор плато 50% (структурное: fb-RX платы A глохнет в её же
  TX-бёрсты, duty 57% при -p 4000), окно fb-TX -20..-10, пересмотр критерия
  доставки (STATUS кумулятивен, 5 Гц эффективных достаточно), TDD не нужен
- BENCHMARK.md: framegen 570.6 мкс/кадр 255 Б -> STATUS 10 Гц ~0.6% ядра
2026-07-16 11:42:08 +03:00

12 KiB
Raw Blame History

BENCHMARK.md — протокол замеров CPU (ofdmflexframesync на ARM Pluto)

Этот документ — основание для выбора sample rate. При любом изменении PHY-параметров (M, CP, модуляция, payload) бенчмарк перегоняется и таблица обновляется в том же коммите (правило №6 CLAUDE.md).

Зачем

ofdmflexframesync (liquid-dsp) — самый дорогой блок тракта. Если его пропускная способность на одном ядре ниже sample rate, приёмник отстаёт от эфира: IIO-буферы какое-то время амортизируют, затем переполняются и кадры теряются лавинообразно. Опасность в том, что на редких пакетах система выглядит работающей — отказ проявляется только под нагрузкой.

Методика

Инструмент: src/ofdm_bench.c. Генерирует эталонный буфер (8 кадров + гэпы, duty cycle кадров ~57%), делает int16-версии «сигнал+шум» (SNR ~28 дБ) и «чистый шум», прогоняет каждый через синхронизатор ≥5 с. Конвертация int16→float входит в замер (как в receiver.c). Процесс пинится на ядро 0 (sched_setaffinity).

Меряются два режима, лимитирует худший:

  • шум — стоимость дежурного поиска преамбулы (>90% времени в линии);
  • сигнал+кадры — полный цикл демодуляции под нагрузкой.

Валидация замера: CRC ok должен равняться 8 × число проходов (все сгенерированные кадры пойманы и декодированы). Иначе сборка кривая и цифрам верить нельзя.

Окружение эталонного замера

Параметр Значение
Дата 10-07-2026
Плата Pluto+ (Zynq-7020), 2 × Cortex-A9 @ 667 МГц, NEON, 1 ГБ
Прошивка v0.38-4-g95aad-dirty
liquid-dsp 1.8.0, с FFTW 3.3.10 (NEON)HAVE_LIBFFTW3F 1 в config.log
Сборка -O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -ffast-math -static
Кадр M=64, CP=16, taper=4, QPSK, CRC32, FEC none, payload 1275 Б, header 8 Б
Ядро одно (affinity CPU0)

Результаты

буфер: 143360 сэмплов, 8 кадров на проход

шум (поиск):     5.3185.324 Msps   (186 проходов; кадров 0 — норма)
сигнал+кадры:    2.3382.339 Msps   (82 прохода; 656/656 CRC ok — валидно)

Разброс между тремя запусками < 0.1% — замер чистый.

Выводы (проектные решения)

Sample rate Запас (кадры) Вердикт
3.84 MSPS 0.61× ЗАПРЕЩЁН: не realtime, отказ под нагрузкой
1.92 MSPS 1.22× рабочий: второе ядро свободно под IIO/RS/IO
1.00 MSPS 2.34× резерв, если 1.92 окажется впритык в реальном тракте

⚠️ ПЕРЕСМОТРЕНО 14-07-2026 (см. «Разбивка стоимости по стадиям»). «Запас 1.22×» измерен при duty кадров 0.571 (гэп 7680). На СПЛОШНОМ потоке кадров (duty→1, как при -p 0) синхронизатор тянет лишь 1.65 Msps = 0.86× от 1.92 — запаса нет. Строка «1.92 MSPS 1.22×» верна только для редких пакетов; под непрерывной нагрузкой линия требует TX-паузы.

  • Полезная скорость при 1.92 MSPS: ~1.31.5 Мбит/с (≈160190 КБ/с) после RS(255,223), преамбул и служебки.
  • Поиск на шуме (5.32 Msps) не лимитирует ни один из режимов.
  • RS-декод в замер не входит: его стоимость мала ПЕРЕСМОТРЕНО 14-07-2026: стоимость RS НЕ мала — 275 мкс/блок = 1.37 мс/кадр = 26% эфира кадра. Это крупнейшая переносимая стоимость на потоке синхронизатора; выносится на второе ядро отдельной стадией.

Разбивка стоимости по стадиям (14-07-2026) — диагностика TX-паузы

Двухпоточный RX (поток A: refill+convert на ядре 1; поток B: sync+RS на ядре 0) НЕ убрал паузу: на 1 МБ чисто только при -p 8000 (хуже базы §12.4). ofdm_bench расширен (-g гэп, -P payload, -H hog-контеншн, разбивка conv/sync/RS) и вскрыл причину. Плата B (RX), ядро 0, liquid-dsp 1.8.0.

Артефакт прежней методики: «2.34 Msps» — это duty 0.571

«2.34» — смесь быстрого поиска по шуму (5.6 Msps) и медленной демодуляции. На сплошных кадрах (-g 0, duty 1.0 — что видит RX на -p 0):

Режим duty 0.571 (-g 7680) duty 1.000 (-g 0)
шум sync-only 5.61 Msps 5.48 Msps
сигнал conv+sync 2.33 Msps 1.62 Msps
сигнал sync-only 2.40 Msps 1.65 Msps
только конвертация 81 Msps 85 Msps

Синхронизатор сам тянет 1.65 Msps на сплошных кадрах = 0.86× от 1.92. При duty→1 он в принципе не realtime — -p 0 недостижим на этом PHY/CPU при любой архитектуре потоков. (-P 1024, геометрия FEC none: то же, 1.67.)

Стоимость RS-декода — то, что уносится на ядро 1

fec_decode RS(255,223), искусственные байт-ошибки:

Ошибок/блок мкс/блок мс/кадр (5 бл) % эфира кадра (5.33 мс)
0 (чистый) 275 1.37 25.8%
4 303 1.52 28.4%
16 (предел RS) 387 1.93 36.2%

При EVM 33 дБ почти все блоки чистые → рабочая цифра ~26% эфира.

Конвертация int16→float — практически бесплатна

85 Msps (44× запас). conv+sync (1.62) vs sync-only (1.65) → разница ~2%. Вынос конвертации на ядро 1 (что и сделал двухпоток) экономит потоку B лишь ~2% — вот почему он не помог и слегка навредил (оверхед потоков + drop-newest рвёт lock на выброшенных кусках → попутные битые кадры).

Контеншн общего L2/DDR — пренебрежимо

-H (hog-поток конвертации на соседнем ядре, ~35 МБ/с через общий L2): sync-only 1.649 vs 1.652 без него = 0.2%. Гипотеза кэш-контеншна от потока A ОТВЕРГНУТА — у ядра 1 достаточно ресурса под RS.

Модель p_min (realtime @ 1.92 Msps)

Условие на период кадра: sync(кадр) + sync(шум в паузе) + RS ≤ эфир(кадр+пауза).

Что на ядре 0 (поток B) p_min модель факт (колено Overrun)
sync + RS + conv (однопоток, было) ~56 мс -p 6000 чисто (§12.3/12.4)
sync + RS (двухпоток) ~3.4 мс -p 4000 ещё Overrun, -p 8000 чисто
sync only (3-стадия, реализовано) ~1.33 мс -p 2500 чисто, -p 2000 колено (§12.6)

Итог (§12.6): 3-стадийный конвейер реализован, RS вынесен на ядро 1 (FQ drops = 0 на всех паузах — RS не узкое место). Рабочая точка -p 3000 (вдвое меньше прежней), соук 10 МБ чист (Overrun 0, PER 0.26% — SNR-пол). Эмпирическое колено ~2500 мкс — выше нуль-запасной модели 1333 из-за джиттера планировщика (запас ~2×). Throughput 90 → 120 КБ/с = ×1.36 (не ×45: эфир кадра 5.33 мс доминирует над паузой). Абсолютный -p 0 недостижим — sync сам 0.86× realtime; лосслесс на объёме — групповым FEC (README §12.7), ARQ или дешевле PHY (M=32/CP=8, резерв п.1).

⚠️ Соук-точка (README §12.7, 2026-07-15): -p 3000 держит Overrun 0 на коротких прогонах (≤1 МБ), но на 10 МБ-соуке ядро 0 всё же срывается (~60 Overrun, потери 2.7%) — рабочая точка §12.6 снята на коротких прогонах. Для длинных передач / группового FEC — -p 4000 (Overrun 0, ~95 КБ/с).

Стоимость framegen короткого кадра (16-07-2026) — обратный канал STATUS

Перед включением обратного канала (README §10 п.7, этап 2) замерена сборка+ модуляция короткого кадра 255 Б (STATUS = 1 RS-блок) — bench_framegen в составе штатного прогона ofdm_bench (плата A, ядро 0):

framegen кадр 255Б    570.6 мкс/кадр    1753 кадров/с   (STATUS 10 Гц ≈ 0.571% ядра)

STATUS 10 Гц на потоке C приёмника стоит ~0.6% ядра 1 — пренебрежимо. Геометрия PHY не меняется (та же сетка M=64/CP=16, тот же rate) → таблицы выше в силе, правило №6 CLAUDE.md соблюдено.

Неисследованные резервы (если понадобится > 1.92 MSPS)

  1. M=32/CP=8 — дешевле FFT и эквалайзер; цена: выше доля пилотов, чувствительнее к CFO. Прогнать бенчмарк с этими параметрами. Единственный рычаг поднять сам синхронизатор выше 1.65 Msps (для реального -p 0).
  2. Двухпоточный конвейер — вынос конвертации на ядро 1 ИЗМЕРЕНО 14-07-2026: конвертация почти бесплатна (85 Msps), вынос дал ~2%, паузу не убрал. Рабочий вариант — 3-стадийный конвейер с выносом RS-декода (26% эфира) на ядро 1: p_min ~1.32 мс вместо ~8 мс. См. «Разбивка по стадиям».
  3. Сравнение NEON vs VFP — пересборка без -mfpu=neon (с -mfpu=vfpv3) даёт цифру вклада векторизации для отчёта.

Как воспроизвести

make bench
scripts/deploy.sh bench <ip_платы>
ssh root@<ip_платы> '/tmp/ofdm_bench 1.92'         # штатный duty 0.571 (репро базы)
ssh root@<ip_платы> '/tmp/ofdm_bench -g 0 1.92'    # duty 1.0: реальный потолок RX
ssh root@<ip_платы> '/tmp/ofdm_bench -g 0 -H 1.92' # + контеншн второго ядра
# критерий приёмки realtime: "сигнал sync-only" при -g 0 ≥ 1.92 Msps.
# Сейчас 1.65 < 1.92 → realtime без TX-паузы недостижим (см. разбивку выше).

Опции: -g гэп (сэмплов; 0 = сплошные кадры), -P payload (1275=rs8, 1024=none), -H hog-поток контеншна на соседнем ядре, -c ядро замера, позиц. — цель Msps.