Files
Pluto-SDR/docs/BENCHMARK.md
Maxim 4bf1a6df7b docs: group-FEC 16+2 задокументирован, рабочая точка соука -p 4000 (§12.7)
- README §12.7 — журнал этапа (расчёт 16+2, таблица прогонов, находка overrun)
- README §10 — перенумерация: п.6=group-FEC, ARQ/FDD→п.7, туннель→п.8, видео→п.9
- README §9 — техдолг #6 обновлён (group-FEC закрывает симплекс-потери)
- docs/BENCHMARK.md, CLAUDE.md, test_file_link.sh — соук-точка -p 4000
- transmitter.c — снят -Wdiscarded-qualifiers на crc_generate_key (const-каст)
2026-07-15 09:48:26 +03:00

173 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.**
При duty1 он в принципе не 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 КБ/с).
## Неисследованные резервы (если понадобится > 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`) даёт цифру вклада векторизации для отчёта.
## Как воспроизвести
```bash
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.