Производительность rtl_sdr на Raspberrypi

Очень часто в сети можно встретить гиды по быстрой сборке на коленке анализатора радио с помощью rtl_sdr и raspberrypi. Большинство таких гидов ограничивается достаточно простым описанием: “возьмите одно, вставьте другое, потом немного питоновских скриптов и у вас всё получится”. Для новичков и просто любителей DIY - это прекрасный подход. Можно достаточно быстро познакомится с технологией и увидеть результат. Но что делать если нужно собрать что-то более продвинутое? Что если важна производительность? Для этого необходимо провести более серьезные тесты.

Один из таких тестов - производительность системы во время записи сигнала. Дело в том, что есть несколько способов обработки сигнала с rtl_sdr:

  1. В реальном времени. Сигнал считывается из rtl_sdr и сразу же демодулируется. Результат либо сохраняется на диск, либо отправляется по сети дальше.
  2. В отложенном режиме.
  • предобработки. Сигнал считывается, немного трансформируется и сохраняется на диск. После завершения наблюдения он демодулируется. Например, во время пролёта спутника, сигнал считывается, децимируется и сохраняется на диск. Обычно это делается для того, чтобы уменьшить размер файла на диске. rtl_sdr поддерживает минимальную ширину 240 КГц. Если ширина сигнала меньше, например, 150 КГц, то имеет смысл уменьшить частоту дискретизации.
  • сырые данные. Сигнал считывается и напрямую сохраняется в файл. Такой вариант наиболее быстрый, поскольку не требует обработки. С другой стороны он наиболее требовательный к размеру диска.

Описание теста

В своём проекте r2cloud я занимаюсь обработкой телеметрии различных спутников. Поэтому мне интересно было померить поведение системы при обработке сигнала в отложенном режиме. Для этого я провёл несколько тестов:

  • предобработка. В этих тестах я считывал данные из rtl_sdr и делал децимацию с помощью программы sox. Передачу данных от одной программы к другой можно сделать через pipe.
    • pipe сделан в Java. Поскольку сама обработка сигнала написана на Java, то и pipe я реализовал на Java. Я создавал sox процесс, затем rtl_sdr процесс, и вручную копировал байты из stdin rtl_sdr в sox. Последний писал результат на диск в файл. Я сделал специальный проект rtlsdr-pipe-tester, чтобы лучше локализовать алгоритм работы.
java -jar rtlsdr-pipe-tester-0.0.1-SNAPSHOT-jar-with-dependencies.jar
  • pipe сделан в bash. То же самое, только команды запускаются в bash. Для измерения скорости записи на диск использовалась команда pv.
rtl_sdr -f 137900000 -s 1440000 -g 45 -p 0 - | pv --numeric --bytes 2>raw.txt | sox --type raw --rate 1440000 --encoding unsigned-integer --bits 8 --channels 2 - /tmp/test.wav rate 150000
  • сырые данные
  • raspberrypi 1 и raspberrypi 3. Новая версия имеет большее количество процессоров. Мне хотелось понять, влияет ли количество процессоров на производительность задачи.
  • использование usbfs zerocopy (появилось в rtl_sdr 0.6.x) и нет. В этом режиме процесс может читать память драйвера напрямую, без копирования через память ядра Linux. В теории это должно уменьшить нагрузку на систему и сделать её более стабильной.

RaspberryPI 1

Среднее значение 820 кбайт/сек для bash и 800 кбайт/сек для Java. Это совсем не то что должно быть. Дело в том, что я запрашиваю 1440000 сэмплов в секунду. Каждый сэмпл содержит в себе I и Q пару по одному байту. Итого мне нужно 2880 кбайт/сек. Похоже сэмплы теряются. Загрузка CPU при этом следующая:

На графики видны падения использования CPU. Согласно полной статистике из vmstat в это время происходит 100% wa. Система явно не справляется с нагрузкой.

RaspberryPI 1 - zerocopy

Большой разброс в значениях в обработке с помощью bash pipe - на самом деле хороший признак. Это значит, что система успевает обработать данные и периодически наполняет внутренние буфера для копирования. Конечно, можно было бы и лучше - сразу копировать данные, как только они появляются. Тем не менее среднее значение в районе 2880 кбайт/сек, что очень хорошо.

Копирование с помощью Java pipe по-прежнему 800 кбайт/сек, что значит выигрыша zerocopy не даёт.

При этом график потребления CPU выглядит достаточно интересным. С одной стороны, видно, что потребление при bash pipe снизилось до 35 процентов. С другой стороны, всё равно остались участки, где wa 100 и CPU простаивает. У меня есть подозрение, что нужно оптимизировать запись на диск. Дело в том, что в raspberrypi диск - это флэш карта. Видимо она не справляется с нагрузкой.

RaspberryPI 3

Для RaspberryPi 3 ситуация выглядит получше. Похоже производительность хватает, чтобы обработать 2880 кбайт/сек.

Потребление CPU около 19%. Для 4-х ядерного процессора RaspberryPi 3 это значит, что одно ядро почти полностью загружено.

RaspberryPI 3 - zerocopy

Скорость обработки более-менее стабильная 2880 кбайт/сек.

А вот загрузка CPU достаточно неожиданная. При использовании zerocopy, я бы ожидал, что она уменьшится. В реальности же она осталась неизменной. Возможно операционная система запустила sox на втором ядре. Тогда небольшая загрузка на первом ядре (rtl_sdr) и небольшая нагрузка на втором ядре (sox) в сумме как раз дают около 19% загрузки CPU в сумме.

Выводы

  1. RaspberryPi 3 значительно мощнее RaspberryPi 1
  2. Использование rtl_sdr 0.6.x c usbfs zerocopy значительно уменьшает нагрузку на систему