- 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% ядра
187 lines
12 KiB
Markdown
187 lines
12 KiB
Markdown
# 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.318–5.324 Msps (186 проходов; кадров 0 — норма)
|
||
сигнал+кадры: 2.338–2.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.3–1.5 Мбит/с (≈160–190 КБ/с)
|
||
после 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 (однопоток, было) | ~5–6 мс | `-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** (не ×4–5:
|
||
эфир кадра 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.3–2 мс вместо ~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. |