Перевод статьи разработчика Samsara об опыте использования Go на автомобильном роутере со 170МБ ОЗУ.
В Самсаре мы разрабатываем автомобильные маршрутизаторы , которые обеспечивают телеметрию двигателя в режиме реального времени через МОЖЕТ шина, данные от беспроводных термодатчиков через Bluetooth с низким энергопотреблением и подключение к Wi-Fi. Эти маршрутизаторы имеют очень ограниченные ресурсы.
В отличие от серверов с 16 ГБ ОЗУ, наши роутеры имеют всего 170 МБ и только одно ядро.
Наша новая камера CM11 установлена в кабине.
Ранее в этом году мы выпустили видеокамеру в кабине, чтобы повысить безопасность транспортных средств для наших клиентов.
Эта камера по сути является периферийным устройством для нашего роутера, генерирующим много данных.
Она записывает 1080p H.264 видео со скоростью 30 кадров в секунду.
Наша первая реализация службы камеры, которая представляла собой отдельный процесс на маршрутизаторе, потребляла 60 МБ, то есть почти половину всей доступной памяти, но мы знали, что можем добиться лучших результатов.
Мы буферизировали только 3 секунды видеопотока со скоростью 5 Мбит/с, а 60 МБ было достаточно, чтобы хранить в памяти полные 90 секунд видео, поэтому мы решили посмотреть, как можно сократить использование памяти.
Реализация услуги камеры видеонаблюдения Сервис камеры устанавливает параметры записи в камере, затем получает и сохраняет видео.
Сохраненное видео H.264 затем конвертируется в mp4 и загружается в облако, но это происходит немного позже в фоновом режиме.
Мы решили написать сервис полностью на Go, чтобы его можно было легко интегрировать с остальной частью нашей системы.
Это позволило быстро и легко написать первую реализацию сервиса, но она занимала половину доступной памяти на устройстве, и у нас началась паника ядра из-за нехватки памяти.
Наши задачи были следующими:
- сохраняйте размер резидентного набора (RSS) процесса на уровне 15 МБ или меньше, чтобы у других служб также оставалась память для своих задач.
- оставьте не менее 20% общей памяти свободными, чтобы допустить периодические всплески использования памяти.
Поскольку изначально мы буферизовали 3 секунды, мы попробовали вообще не буферизовать и сохранять на диск по одному кадру.
Этот подход не сработал, потому что накладные расходы на запись в 20 КБ (средний размер кадра) со скоростью 30 кадров в секунду снижали пропускную способность и увеличивали время отклика в тех случаях, когда мы просто не могли справиться с входящим потоком.
Слева — исходная архитектура буферизации: мы буферизировали около 90 кадров видео перед записью на диск.
Справа — подход без буферизации: каждый кадр записывается напрямую на диск.
Затем мы попытались буферизовать фиксированное количество байтов.
Мы воспользовались пакетом io из стандартной библиотеки Go и использовали буфио.
Писатель , который обеспечивал буферизованную запись в любой тип io.Writer, даже если базовая структура не поддерживала буферизацию.
Это позволило нам легко указать, сколько байтов мы хотим буферизировать.
Следующей задачей было найти оптимальный компромисс между размером буфера и задержкой ввода-вывода.
Слишком большой буфер и мы можем потерять слишком много памяти, но с другой стороны слишком много времени чтения/записи и мы уже не сможем справиться с поступающим видео с камеры.
Мы провели простой тест, изменяя размер буфера от 1 КБ до 1 МБ и измеряя время, необходимое для записи 3 секунд (или около 1,8 МБ) видео на диск.
Размер буфера и время записи На графике четко виден переломный момент в районе 64 КБ — хороший выбор, который не использует слишком много памяти и при этом достаточно быстр, чтобы не терять кадры.
(Такая заметная разница во времени объясняется реализацией флэш-памяти).
Это изменение буферов уменьшило использование памяти на порядок мегабайт, но все же не ниже предела, к которому мы стремились.
Окончательная архитектура: перед записью мы всегда буферизуем 64 КБ.
Следующим шагом было профилирование использования памяти сервисом с помощью встроенного профилировщика Go. ппроф .
Мы выяснили, что на самом деле процесс занимал очень мало времени, но со сборщиком мусора происходило что-то подозрительное.
Настройка сборщика мусора Сборщик мусора Go использует малое время отклика и простота .
Он имеет единственный параметр настройки, GOGC — процент, который контролирует соотношение общего размера кучи к размеру, доступному процессу.
Мы поигрались с этим параметром, но особого эффекта не было, так как память, освобожденная после сборки мусора, не сразу возвращалась в операционную систему.
После анализа Перейти исходный код , мы обнаружили, что сборщик мусора передает операционной системе неиспользуемые страницы памяти только раз в 5 минут. Поскольку это позволяет избежать постоянных циклов выделения/освобождения памяти при создании и удалении больших буферов, это улучшает время отклика.
Но для приложений, чувствительных к памяти, таких как наше, это был не лучший вариант. Наш случай не очень чувствителен к времени отклика, и мы предпочли бы пожертвовать меньшим временем отклика меньшим использованием памяти.
Таймаут возврата памяти операционной системе изменить нельзя, но в Go для этого есть функция debug.FreeOSMemory , который запускает сборку мусора и возвращает принудительно освобожденную память операционной системе.
Это было удобно.
Мы изменили наш сервис камеры, чтобы он вызывал эту функцию каждые 5 секунд и увидели, что параметр RSS уменьшился почти в 5 раз до приемлемых 10-15МБ! Уменьшение потребления памяти, конечно, не происходит бесплатно, но в нашем случае оно подошло, так как у нас не было гарантий реального времени и мы могли немного пожертвовать временем отклика из-за более частых пауз при сборке мусора.
Если вам интересно, почему это сработало: мы периодически загружаем видео в облако, и это приводит к резкому увеличению потребления памяти примерно на 15 МБ.
Мы можем смело допускать такие пики, если они длятся несколько секунд, но не дольше.
Пиковое значение 30 МБ и GOGC=200 % означает, что сборщик мусора может выделить до 60 МБ.
После пика Go не освобождает память в течение 5 минут, но вызовом debug.FreeOSMemory мы сократили этот период до 5 секунд. Заключение Добавление новых периферийных устройств, с которыми мог работать наш маршрутизатор, серьезно сказалось на ограничениях памяти.
Мы экспериментировали с различными стратегиями буферизации, чтобы уменьшить использование памяти, но в конечном итоге нам помогла настройка сборщика мусора Go на другое поведение.
Для нас это стало некоторой неожиданностью — обычно при разработке на Go не думаешь о распределении памяти и сборке мусора, но в наших условиях нам пришлось это сделать.
Нам удалось сократить потребление памяти в приятные 5 раз и добиться того, чтобы на роутере всегда было 50 МБ свободной оперативной памяти, поддерживая загрузку видео в облако.
Теги: #Go #gc #gc #linux #iot #Go #Разработка для Интернета вещей
-
Национальные Языковые Традиции
19 Oct, 24 -
Переход С Windows На Linux. Были Ли Попытки?
19 Oct, 24