Lab048: accelerate BPSK receiver to real-time

This commit is contained in:
LittleSam129
2026-09-03 14:42:02 +03:00
parent 6fb7e4ec54
commit ac6cc792b8
11 changed files with 1047 additions and 9 deletions

View File

@@ -176,6 +176,30 @@
`0.030964473262429237` не менялся, второй RF-сеанс не выполнялся.
- **Журнал:** записи 033-034 ([`PROJECT_LOG.md`](../PROJECT_LOG.md)).
## Lab048
- **Цель:** профилирование и ускорение доказанного автономного BPSK-приёмника
без изменения детектора, PHY, FEC, CRC и порога.
- **Статус:** завершена успешно только на сохранённой RX-записи Lab047;
обработка ускорена с 37,423910 до 13,966058 с, в 2,679633 раза. Для 15,0 с
эфира отношение времени обработки равно 0,931071:
`REAL-TIME CAPABLE = YES`.
- **Результат:** сохранены 36/36 кадров, FEC PASS 36/36, CRC PASS/FAIL 36/0,
полный JPEG, точный SHA-256, 0 ошибочных байтов и битов. Порог
`0.030964473262429237` не изменён.
- **Ограничение:** приблизительный пик памяти остаётся около 1,44 ГиБ из-за
16-миллионного блока грубого поиска; запас реального времени около 1,03 с.
- **Основной эксперимент:**
[`experiments/lab048_receiver_performance.py`](../experiments/lab048_receiver_performance.py).
- **Проверки:**
[`tests/test_lab048_receiver_performance.py`](../tests/test_lab048_receiver_performance.py).
- **Документ состояния:**
[`docs/lab048_current_state.md`](lab048_current_state.md).
- **Обработанные данные:** машинный результат и восстановленный JPEG находятся
в [`data/processed/lab048`](../data/processed/lab048).
- **RF:** новый радиосеанс не выполнялся, `RF TX = 0`.
- **Журнал:** запись 035 ([`PROJECT_LOG.md`](../PROJECT_LOG.md)).
## Правило хранения данных
Большие записи IQ, потоки `cs8`, бинарные объекты и другие аппаратные исходные

View File

@@ -0,0 +1,206 @@
# Lab048: профилирование и ускорение автономного BPSK-приёмника
## Состояние
Lab048 завершена успешно только на сохранённой аппаратной RX-записи Lab047.
Новый радиосеанс не выполнялся: `RF TX = 0`, HackRF не запускался. Научные
критерии обнаружения и принятия кадра, BPSK, PRBS7, PHY, FEC, CRC, формат
пакета и порог `0.030964473262429237` не изменены.
Итоговая обработка 15,0 с эфира занимает 13,966058 с. Это в 2,679633 раза
быстрее исходных 37,423910 с и впервые быстрее длительности самой записи:
отношение времени обработки к времени эфира равно 0,931071.
## Исходная контрольная точка
Использована та же запись:
`C:\Users\user\Documents\SDR_Rover_Lab047_raw\20260903_115747\rx_stream.cs8`.
Размер — 240000000 байт, 120000000 комплексных отсчётов, длительность 15,0 с,
SHA-256 `8521bf1b574cd1fe4e0a4f980c00c8664f8bb51551a241e46449e1eb2c77e635`.
Принятые до оптимизации метрики Lab047:
- полное время: 37,423910 с;
- строгая ступень: 32,459969 с;
- среднее время строгой проверки: 0,470434 с;
- строгих кандидатов: 69;
- приблизительный пик памяти: 1545755352 байта, около 1,44 ГиБ;
- действительных кадров: 36;
- CRC PASS/FAIL: `36/0`;
- полный JPEG, SHA-256 совпадает, ошибок байтов и битов нет.
## Базовое профилирование
До изменения кода выполнен один полный проход под `cProfile`. Он сохранил
185828 локальных максимумов, 32414 кандидатов первого каскада, 69 строгих
проверок, 36 действительных кадров, CRC PASS/FAIL `36/0` и точный JPEG.
Накладные расходы профилировщика увеличили полное время до 45,947287 с,
поэтому коэффициент ускорения считается относительно принятого измерения без
профилировщика 37,423910 с.
Десять функций по суммарному времени:
| Функция | Вызовы | Суммарно, с |
|---|---:|---:|
| `_run_with_fixed_candidate_rule` | 1 | 45,945 |
| `stream_process_capture` | 1 | 45,930 |
| `decode_candidate_from_memmap` | 69 | 40,912 |
| `_extract_soft_window` | 105 | 25,697 |
| `_matched_waveform` | 105 | 21,199 |
| `fftconvolve` | 252 | 13,660 |
| `_freq_domain_conv` | 252 | 12,811 |
| `__ua_function__` FFT | 756 | 11,889 |
| `root_raised_cosine_taps` | 175 | 11,885 |
| `_execute_nD` FFT | 756 | 11,881 |
Десять функций по собственному времени:
| Функция | Вызовы | Собственное время, с |
|---|---:|---:|
| `scipy.fft c2c` | 756 | 11,222 |
| `_matched_waveform` | 105 | 5,704 |
| `numpy.isclose` | 1400175 | 5,407 |
| `upfirdn.apply_filter` | 9 | 3,171 |
| `_timing_offsets` | 105 | 2,859 |
| `root_raised_cosine_taps` | 175 | 2,306 |
| `_scale_and_carrier` | 70 | 1,801 |
| генератор `numpy.isclose` | 7000875 | 1,160 |
| `_cs8_window` | 113 | 1,105 |
| `_freq_domain_conv` | 252 | 0,917 |
Профиль показал повторное формирование одной опорной преамбулы 70 раз и
одинаковых RRC-коэффициентов 175 раз. Согласованный фильтр вызывался 105 раз:
69 коротких окон для PHY и 36 полных окон. Временная синхронизация выполняла
1601 Python-итерацию для каждого окна. Витерби занимал лишь 0,875 с на все 36
кадров, CFO/фаза — 0,054 с на 105 вызовов, PHY-заголовок — 0,006 с. Поэтому
FEC, CFO, фаза, PHY и CRC не оптимизировались.
## Выполненные оптимизации
Изменены только вычислительные детали приёмника:
1. Опорная PRBS7-преамбула формируется один раз, кэшируется и защищается от
записи.
2. RRC-коэффициенты формируются один раз, хранятся как неизменяемый `float32`.
3. Та же полная линейная RRC-свёртка вычисляется методом overlap-add
(`oaconvolve`) вместо единой FFT (`fftconvolve`).
4. Неизменяемые векторы переноса несущей кэшируются по началу и длине окна;
кэш ограничен четырьмя записями.
5. Рабочие массивы согласованного фильтра переведены с `complex128` на
`complex64`. Максимальное абсолютное расхождение с прежним фильтром на
проверочном шумовом массиве равно `1,263e-6`, менее `5e-7` от пика.
6. Преамбульная временная синхронизация считает те же 1601 нормированные
оценки группами по 400 фазам и не вычисляет второй идентичный набор оценок.
Прямой тест прежнего `fftconvolve/complex128` и нового
`oaconvolve/complex64` требует абсолютное расхождение не более `2e-6` и
отношение максимального расхождения к пику менее `5e-7`. Отдельные тесты
подтверждают точность и неизменяемость кэшей, ограничение их размера, тот же
timing offset и отсутствие TX/expected-frame параметров у независимого API.
Существующие сквозные тесты защищают те же PHY, FEC, CRC и полезные данные.
## Единственный итоговый полный проход
После программного шлюза выполнен один итоговый полный проход той же записи.
Первый вызов нового командного файла завершился до импорта проекта из-за
отсутствующего загрузочного пути: RX не открывался и файл-свидетель не был
создан. После исправления загрузочного блока выполнен ровно один фактический
полный проход; автоматического повтора нет.
Независимая стадия не получала ожидаемое число кадров, TX offsets, номера
TX-фрагментов, переданный JPEG, пакеты или кодированные биты. Только после
`completed = true` и `OBJECT COMPLETE = YES` выполнена диагностическая сверка
с TX.
Результат:
- полное время: 13,966058 с;
- грубый поиск: 4,776677 с;
- строгая ступень: 9,171629 с;
- среднее время строгой проверки: 0,132922 с;
- ускорение полного прохода: 2,679633 раза;
- отношение обработки к длительности эфира: 0,931071;
- `REAL-TIME CAPABLE = YES`;
- локальных максимумов: 185828;
- кандидатов первого каскада: 32414;
- строгих проверок: 69;
- пропущено внутри уже принятых областей: 32345;
- точных дубликатов: 0;
- PHY-заголовок PASS/FAIL: `36/33`;
- FEC attempts/PASS/FAIL: `36/36/0`;
- CRC PASS/FAIL: `36/0`;
- действительных кадров и фрагментов: `36/36`;
- отсутствующих фрагментов: 0;
- `OBJECT COMPLETE = YES`.
Только после независимого вердикта подтверждены 36/36 переданных кадров,
19 ошибок кодированных бит до FEC, 0 ошибок протокольных бит после FEC и 0
ложных CRC PASS. Восстановленный JPEG имеет 8724 байта, RGB 320 x 240,
декодируется Pillow и имеет SHA-256
`e913dab9ac32795250880a4be67fbbbf89a4f96536842476ab591d1acaafc02a`.
SHA-256 переданного файла совпадает; ошибочных байтов 0, ошибочных битов 0.
## Время стадий после оптимизации
Измерения вложенных функций пересекаются по времени:
| Стадия | Вызовы | Суммарно, с |
|---|---:|---:|
| преобразование CS8 в `complex64` | 113 | 1,487798 |
| уточнение начала | 69 | 3,121533 |
| согласованный фильтр | 105 | 3,282575 |
| временная синхронизация | 105 | 1,270783 |
| CFO, фаза и мягкие решения | 105 | 0,042236 |
| PHY-заголовок | 105 | 0,004111 |
| FEC, разбор пакета и CRC | 36 | 0,645559 |
| Витерби | 36 | 0,627109 |
Следующее узкое место верхнего уровня — грубый поиск, 4,776677 с. Внутри
строгой ступени сопоставимы согласованный фильтр, 3,282575 с, и уточнение
начала, 3,121533 с. Запас реального времени пока мал: около 1,03 с на 15 с
эфира.
## Память
Консервативная пиковая оценка осталась 1545755352 байта, около 1,44 ГиБ,
поскольку максимум создаёт неизменённая обработка 16-миллионного блока
грубого поиска. Крупнейшие явно управляемые массивы:
| Назначение | dtype | shape | MiB |
|---|---|---:|---:|
| окно грубого поиска | `complex64` | 16000000 | 122,070 |
| прореженный поток | `complex64` | 800000 | 6,104 |
| вектор переноса грубого поиска | `complex64` | 800000 | 6,104 |
| оценки корреляции | `float32` | 773561 | 2,951 |
| полное строгое окно | `complex64` | 2306400 | 17,596 |
| кэшированный вектор несущей | `complex64` | 2306400 | 17,596 |
| основная полоса строгого окна | `complex64` | 2306400 | 17,596 |
| выход согласованного фильтра | `complex64` | 2310400 | 17,627 |
| RRC-коэффициенты | `float32` | 4001 | 0,015 |
Переход строгой ступени с `complex128` на `complex64` уменьшил её рабочие
массивы, но не общий пик всего процесса. Уменьшение блока грубого поиска в
Lab048 не выполнялось, чтобы не менять блочные границы и результаты детектора.
## Артефакты
- `experiments/lab048_receiver_performance.py`;
- `tests/test_lab048_receiver_performance.py`;
- `data/processed/lab048/final_processing_attempt.json`;
- `data/processed/lab048/result.json`;
- `data/processed/lab048/recovered_image.jpg`.
Файл `cProfile` сохранён только как временный локальный профиль вне проекта:
`C:\Users\user\AppData\Local\Temp\lab048_baseline_20260903_6fb7e4e.pstats`.
## Проверки
- связанные тесты после поэтапных изменений: 169 passed;
- `python tools/quick_gate.py`: 547 тестов и 11 функциональных проверок, PASS;
- `python -m pytest tests/ -q`: 547 passed, 0 failed;
- `git diff --check`: PASS.
## Следующий этап
STOP. Не переходить к Lab049, видео, QPSK, повышению символьной скорости или
новому RF-сеансу без отдельной постановки. `RF TX = 0`; HackRF не запускать.