24 KiB
Lab051 — вычислительная архитектура потокового BPSK-приёмника
Дата: 8 сентября 2026 года. SOFTWARE ONLY. RF TX = 0.
Результат и граница вывода
Программный эксперимент завершён; цели реального времени и свежести не достигнуты. В полном шестисекундном потоке получены 240/240 физических пакетов и 30/30 JPEG с точным содержимым. Обработка 6,044416 с синтетического IQ заняла 38,630132 с, отношение 6,391045. При MAX_VIDEO_AGE=200 мс FRESH DISPLAY FPS=0; не выполнен даже минимальный критерий >0.
Короткий шумный сценарий Lab050 ускорен примерно в 48,21 раза относительно исторического замера: 55,157553 с → медиана 1,144176 с трёх проходов. Его processing/RF = 3,534193, поэтому и этот вариант не работает в реальном времени. Среднее успешного кандидата — 17,7675 мс; полная стоимость короткого опыта — 47,674 мс/пакет, а длинного потока — 160,959 мс/пакет. Последние величины включают обработку ложных кандидатов и фона.
Модуляция BPSK, PRBS7, PHY-заголовок, фрагментация, интерливер, FEC R=1/2 K=7, 171/133 octal, soft-метрика и CRC не менялись. GPU и multiprocessing не использовались. Новых зависимостей нет. Аппаратура не перечислялась и не запускалась.
Git и аудит до реализации
Исходный main: cdf5396e807b83f82a264632dc46f71428fcd616.
Проверены HEAD/status/diff stat/diff check, затем показан точный staged scope.
По прямому разрешению пункта 0 задания создан коммит:
60691a09f193f36983c7174c5252ed0897c559e9
Lab050: model fresh-frame SDR video stream
В него вошли ровно девять файлов: PROJECT_LOG.md, docs/LABS_INDEX.md, experiments/lab046c_offline_recovery.py, protocol/video_frame_scheduler.py, docs/lab050_current_state.md, experiments/lab050_streaming_receiver.py, experiments/lab050_video_stream.py, tests/test_lab050_video_stream.py, data/processed/lab050/receiver_benchmarks.json. 1769 добавлений, 12 удалений. Большой result.json, профили JPEG и посторонние локальные файлы не добавлялись.
Lab051 начата от этого HEAD. Изучены Lab046B/C, Lab047, Lab048, Lab050, Lab043/044/045 и свёрточный декодер. Ядро protocol в Lab051 не менялось. Планировщик LATEST_ONLY и ObjectAssembler переиспользованы. Новая видеочередь не создана.
Профиль до оптимизации
Рабочая точка: 500000 символов/с, Fs=8000000 отсчётов/с, 16 отсчётов/символ. Профиль C: 128×96 RGB, JPEG quality 25. Короткий контроль: три изображения, 24 пакета, seed=50050, coded-bit Eb/N0=7 дБ, CFO=+10 Гц, фаза 0,73 рад, чередование полярности, исходное CS8-квантование. Генератор точно прежний, из Lab050.
Путь кандидата: IQ → грубая нормированная корреляция → уточнение старта → согласованный RRC-фильтр → timing → оценка частотного рассогласования CFO → компенсация CFO и фазы → мягкие решения → PHY header → deinterleave → soft Viterbi → protocol parser → CRC. Только полный PHY/FEC/CRC PASS выдаёт пакет. TX-границы, ожидаемое число кадров и содержимое не входят в RX API.
Историческая база Lab050: 55,157553 / 0,323745 = 170,373647. Свежий инструментированный baseline Lab051: 49,614327 с, processing/RF=153,251430. Настольная нагрузка и инструментация влияют на время; эти замеры не подменяют друг друга. Все 2374 кандидата: 24 valid, 2350 false. Средние времена — 72,967 / 20,099 мс.
Главная причина замедления — fallback timing. Номинальное положение равно 416, а диапазон остался −800…+800 отсчётов. Начало выходит за ноль, и прежний быстрый путь возвращается к Python-перебору 1601 смещения. У ложных кандидатов timing занял 41,833 с из 47,232 с строгой обработки. Их matched filter — 0,934 с, уточнение — 2,797 с. Прежняя реализация уже останавливала кандидата при PHY FAIL: тело, Viterbi и CRC на таких входах не запускались.
Для преамбулы и заголовка достаточно 23456 входных отсчётов и 23616 отсчётов после RRC. Полный физический пакет с payload 256 байт требует 92256 входных отсчётов. Длина тела определяется только проверенным PHY-заголовком.
Изменения вычислений
- Децимация сохраняет внутреннюю часть конечного импульсного отклика FIR. При сдвиге буфера заново считаются левый нулевой край и новые правые отсчёты. Коэффициенты resample_poly/kaiser(8) прежние; сравнение с исходной функцией побитно точное. Среднее по всему прежнему окну сохраняется. Корреляция считается только для принадлежащих блоку стартов плюс правый сосед максимума.
- Корреляция известного prefix для уточнения считается один раз для общего участка. Кандидаты используют срезы, с прежней поправкой локального DC. Постоянный probe и неразделимые FFT-пики сохраняют исходный выбор первого максимума; при необходимости вызывается старое уточнение.
- Компенсация известного +50 кГц и RRC выполняются для общего участка. Локальное вычитание среднего и оба нулевых края изолированного окна восстановлены отдельно. Без этих поправок общая фильтрация меняла бы алгоритм. Тело за пределами общего участка фильтруется локально только после PHY PASS.
- Извлечение символов кэшируется по уточнённому положению и длине внутри одного блока. Усечённое окно не подменяется ранее сохранённым полным окном. Кэш уничтожается вместе с блоком; история сессии не сохраняется.
- Timing сохраняет весь прежний диапазон 1601 смещения. Корреляция с разреженной известной преамбулой и разности накопленных энергий заменяют Python-перебор. Тело за последним нужным отсчётом не участвует в этом расчёте. При пиках, неразделимых с точностью float32, возвращается исходный расчёт и его tie-правило.
- CFO по прежним преамбульным символам не изменён. Векторы переноса кэшируются; вектор остаточной частоты строится на символах локального окна. Gain/фаза для probe и полного кадра не объединены: у них различается локальное среднее IQ.
- Viterbi после устранения главного повтора остался существенной долей принятого пакета. Предвычислены четыре различные пары евклидовых branch metrics; две ветви сравниваются непосредственно, с прежним выбором branch 0 при равенстве. Решётка, старт/финиш 0, шесть хвостовых битов и декодированные биты сохранены.
Дополнительный порог преамбулы/новый критерий отсева не введён. Фиксированный порог первого каскада 0.030964473262429237 сохранён. В коротком опыте совпали число кандидатов и все причины отказа с исходным RX. Параллелизм и CUDA не добавлялись.
Профиль стадий
Ячейка: число вызовов / суммарные миллисекунды по соответствующей группе. Это не длительность одного вызова. Общая работа front-end учитывается в том кандидате, который впервые запросил её. Фаза показана без вложенного CFO; время body pipeline не складывается повторно со временем Viterbi/CRC.
| Стадия | Baseline valid (24) | Optimized valid (24) | Baseline false (2350) | Optimized false (2350) |
|---|---|---|---|---|
| Точное уточнение | 24 / 28.974 | 24 / 93.878 | 2350 / 2797.404 | 2350 / 119.743 |
| Согласованная RRC-фильтрация | 48 / 190.656 | 48 / 102.875 | 2336 / 933.965 | 174 / 26.502 |
| Timing: весь диапазон | 48 / 914.632 | 48 / 43.535 | 2336 / 41833.166 | 174 / 138.588 |
| CFO и комплексное усиление | 48 / 10.838 | 48 / 8.442 | 2336 / 485.772 | 174 / 26.385 |
| Фаза/полярность и диагностика | 48 / 8.994 | 48 / 8.324 | 2336 / 424.756 | 174 / 28.438 |
| PHY header | 48 / 1.711 | 48 / 1.452 | 2298 / 67.136 | 2298 / 32.044 |
| Viterbi | 24 / 484.381 | 24 / 132.119 | 0 / 0 | 0 / 0 |
| Protocol parser / CRC | 24 / 0.270 | 24 / 0.211 | 0 / 0 | 0 / 0 |
Грубая локализация — общая стадия: 36 вызовов, baseline 498,040 мс, optimized 89,183 мс. На один false candidate среднее 0,187317 мс, медиана 0,060500 мс; это включает cache hits. Холодное новое окно дороже.
Массивы и память
Число всех аллокаций внутри NumPy/SciPy C не измерено. Line tracer наблюдает массивы в локальных переменных проектного Python: число ниже — нижняя граница, включающая views. Владельцы данных выделены отдельно. Временные выражения и внутренние C-массивы не перечислены. Отдельный tracemalloc-проход измеряет пик отслеживаемой памяти. Эти медленные проходы не включены в CPU benchmark.
| Кандидат / алгоритм | Наблюдаемые ndarray | Из них владельцы | Суммарные байты наблюдаемых владельцев | Пик tracemalloc, байт |
|---|---|---|---|---|
| false / baseline | 2888 | 61 | 6612268 | 2104129 |
| false / optimized | 76 | 61 | 5906101 | 5524392 |
| valid / baseline | 14361 | 8704 | 16681890 | 5885299 |
| valid / optimized | 6594 | 6560 | 12345244 | 7771675 |
Пик холодного кандидата вырос из-за общих промежуточных данных: ускорение не объявляется уменьшением каждого временного массива. В рабочем потоке:
- Пик удерживаемых IQ: 3177728 байт = 3,031 МиБ.
- Проверяемый предел IQ: 3398912 байт; кэш децимации хранится отдельно.
- Инженерная оценка рабочих массивов: 136,161 МиБ. Использован запас прежних 22 размеров IQ-окна плюс 20 размеров на новые массивы и кэши. Это оценка для данного опыта, не доказанный универсальный предел всех аллокаций и не RSS.
- Windows process RSS после потока: 154,105 МиБ; peak RSS процесса — 178,004 МиБ. В него входят Python, библиотеки, генератор, предыдущие замеры и аллокатор.
- Текущий объект ограничен 65536 байтами / 269 фрагментами; C фактически ≤1893 байт.
Длинный тест: 4096 блоков, 33,554432 с модельного времени. Фон нулевой; шум отдельно проверен в основном опыте. После прогрева удерживаются 2653440 байт IQ и 397216 байт децимированных данных; значения не растут. RSS контрольных точек: 156,199–156,250 МиБ, без роста вместе с длительностью. После finish эти буферы освобождены. История JSON относится к отчёту, не к RX.
Непрерывный поток 5 изображений/с
30 воспроизводимых сдвигов исходного data/raw/lab011_scene.jpg, профиль C, 1740–1893 байта. Это искусственная последовательность, не видео камеры. Видеопланировщик LATEST_ONLY использует время capture и завершает активное JPEG. Передатчик знает нижнюю задержку радио/буфера; он не получает будущую стоимость CPU или результаты приёма. Приёмник получает лишь IQ-блоки по 65536 отсчётов.
Шум, фаза, CFO и полярность заданы, но готовность JPEG берётся из измеренного encode. Поэтому точное размещение пакетов и число фоновых кандидатов длинного опыта могут немного меняться между запусками. Короткий baseline использует точно прежний детерминированный сценарий Lab050.
Время CPU-работника для каждого блока:
start=max(previous_end, block_arrival); end=start+measured_push_time.
Сборка/JPEG decode добавляют своё измеренное время. Генерация TX/IQ, SHA-сравнение
с передатчиком и запись JSON исключены. Длительность RX не заменена нулём.
Все данные реально прошли независимый PHY/FEC/CRC и JPEG decode.
| Метрика | Итог |
|---|---|
| INPUT FPS / FRAMES | 5 / 30 |
| TX IMAGES / PHYSICAL PACKETS | 30 / 240 |
| DROPPED BEFORE TX | 0 |
| MAX WAITING VIDEO FRAMES | 1 |
| RX PHYSICAL PACKETS / IMAGES | 240 / 30 |
| JPEG SHA MATCH | 30 / 30 |
| DROPPED STALE AT DISPLAY | 30 |
| DISPLAYED / FRESH DISPLAYED | 0 / 0 |
| RAW RECEIVED FPS в первых 6 с CPU-шкалы | 0.833333 |
| RAW DISPLAY FPS / FRESH DISPLAY FPS | 0 / 0 |
| Возраст декодированных JPEG: min / median / max | 276.515 / 15691.203 / 31150.009 мс |
| MAX_VIDEO_AGE | 200 мс |
| Строгих кандидатов | 147082 |
| Полное время обработки / IQ | 38.630132 / 6.044416 с |
| PROCESSING / RF TIME | 6.391045 |
Возраст относится к декодированным, затем отброшенным JPEG. Показанных изображений нет. RAW DISPLAY и FRESH DISPLAY совпадают, потому что политика не выводит просроченные изображения; RAW RECEIVED приведён отдельно.
Физическое время одного JPEG C: 8×5766/500000 = 92,256 мс. Очередь ожидающих JPEG в этом режиме не задерживает готовые изображения. Время RX за опыт: 38,619085 с; сборка 5,451 мс; JPEG decode 5,596 мс суммарно на 30 JPEG. RF и CPU перекрываются по событийной шкале: эти суммы нельзя просто сложить и назвать измеренной задержкой камеры→экрана. Camera capture latency и display latency UNKNOWN; экран в отчётной модели мгновенный.
Capture lookup используется только после независимой сборки JPEG для метрики возраста/решения модели вывода. В существующем fragment format нет времени съёмки. Готовый независимый аппаратный контроль возраста этим не доказан; формат в Lab051 не расширялся. ID монотонны в одной конечной сессии; wrap/restart не реализованы.
Внешняя IQ-очередь также не реализована. Офлайн-источник подаёт следующий блок по запросу и физически не хранит многосекундный backlog. Если бы вход шёл непрерывно, максимальное отставание 32,632404 с потребовало бы около 2088473848 байт дополнительного complex64 IQ. Поэтому bounded RX state нельзя выдавать за готовую ограниченную live-систему: при таких затратах потребуются потери/переполнение. REAL-TIME CAPABLE = NO.
В длительном опыте встречено значительно больше фоновых кандидатов, чем в коротком: 147082 против 2374. Кэш дал 135057 повторных использований, но осталось 12239 новых окон синхронизации. Следующее ограничение — стоимость этих фоновых событий. Одного ускорения Viterbi недостаточно; подгонять отсев по TX запрещено.
Регрессии и независимая проверка
38 целевых тестов: весь диапазон timing, ненулевые и нулевые ties, локальные DC/края общего RRC, точная децимация при движении окна, Viterbi noisy/tie/invalid, совпадение старого/нового блочного приёма, усечение, буферы и finally. Verifier нашёл два случая округления FFT (timing и refine); оба исправлены, воспроизведены повторно и закреплены тестами. Adversary отдельно проверил malformed PHY (0 body/Viterbi), дубликаты и старые ID, лимиты объекта, усечение и восстановление глобальных подмен.
Сохранённая Lab047-запись 20 ксимв/с повторно обработана штатным Lab048: 36/36, FEC/CRC PASS, JPEG SHA MATCH, 19 ошибок до FEC, 0 после. Исходный путь и SHA JPEG сохранены в regression20k/result.json. Старый режим работает прежним декодером; адаптер Lab051 принимает только 500 ксимв/с и не подменяет аппаратную проверку этого режима.
Итог: quick_gate PASS — 688 тестов, 17 функциональных проверок, 13 штатных пропусков.
Полный pytest — 688 passed за 72,92 с, 0 failed; одно предупреждение доступа к
.pytest_cache. git diff --check PASS. Подробности — в записи 040 журнала.
Управление ELRS/CRSF, локальная защита и ядро protocol не менялись.
Файлы и воспроизведение
- Потоковый RX
- Профилировщик
- Сценарии и событийная шкала
- Тесты
- Baseline
- Три коротких замера и профиль
- Основной поток и все 739 блоков
- Длинный тест памяти
- Наблюдение массивов
- 20 ксимв/с
python experiments/lab051_receiver_profile.py
python experiments/lab051_receiver_performance.py --stage all
python -m pytest tests/test_lab051_streaming_receiver.py -q -p no:cacheprovider
python tools/quick_gate.py
python -m pytest tests/ -q
git diff --check
Старая запись:
python experiments/lab048_receiver_performance.py --run-saved-capture --capture 'C:\Users\user\Documents\SDR_Rover_Lab047_raw\20260903_115747\rx_stream.cs8' --original-result 'C:\Users\user\Documents\SDR_Rover_Lab047_raw\20260903_115747\independent_result.json' --tx-image data/processed/lab047/tx_image.jpg --output-directory data/processed/lab051/regression20k
Локальные replay .npy — ограниченные окна, не полная IQ-запись; в Git не добавлены. Новый полный CS8/IQ файл не создавался. Lab051 git add/commit и push не выполнялись. Следующий рекомендуемый этап — отдельное программное исследование стоимости уникальных фоновых кандидатов в полном режиме 5 кадров/с с тем же PHY/FEC/CRC. RF, QPSK, новый FEC/кодек, 180 МГц и аппаратные 500 ксимв/с не выполнялись. STOP.