メモリが少ないデバイスに移動する

170MBのRAMを搭載したカールーターでGoを使用した経験に関するSamsara開発者による記事の翻訳。







サムサラでは、 CANバスを介したリアルタイムエンジンテレメトリー、 Bluetooth Low Energyを介したワイヤレス温度センサーからのデータおよびWi-Fi接続を提供するカールーターを開発しています 。 これらのルーターのリソースは非常に限られています。 16 GBのRAMを搭載したサーバーとは異なり、当社のルーターは170 MBでコアは1つしかありません。









キャブにマウントされた新しいCM11カメラ。







今年の初めに、当社はお客様の車の安全性を向上させるためにキャブ内に取り付けられたビデオカメラをリリースしました。 実際、このカメラはルーターの周辺機器であり、大量のデータを生成します。 1080p H.264ビデオを30フレーム/秒で記録します。







ルーター上の別のプロセスであるカメラサービスの最初の実装は、60MB、つまり使用可能なメモリのほぼ半分を消費しましたが、より良い結果を達成できることはわかっていました。 5Mpbsで3秒のビデオストリームをバッファリングしただけで、メモリに90秒ものビデオを保持するには60MBで十分だったため、メモリ使用量を削減できる場所を確認することにしました。







カメラサービスの実装



カメラサービスは、カメラの録画パラメーターを設定し、ビデオを受信して​​保存します。 保存されたH.264ビデオは、mp4に変換されてクラウドにアップロードされますが、これは少し遅れてバックグラウンドで発生します。







私たちは、システムの他のコンポーネントと簡単に統合できるように、Goで完全にサービスを作成することにしました。 これにより、サービスの最初の実装を迅速かつ簡単に記述できましたが、デバイスで使用可能なメモリの半分を消費し、メモリ不足によりカーネルパニックが発生し始めました。 私たちのタスクは次のとおりです。









バッファサイズの調整



メモリ使用量を削減する最初の試みは、メモリ内のバッファリング量を単純に削減することでした。 最初に3秒間バッファリングしたため、バッファリングをまったく行わず、一度に1フレームずつディスクに保存しようとしました。 毎秒30フレームの頻度で20KB(平均フレームサイズ)のオーバーヘッド記録を行うと、スループットが低下し、応答ストリームが数字に増加したため、このアプローチは機能しませんでした。









左側は元のバッファリングアーキテクチャです。ディスクに書き込む前に約90フレームのビデオをバッファリングしました。 右側はバッファリングなしのアプローチです。各フレームは直接ディスクに書き込まれます







次に、固定バイト数をバッファリングしようとしました。 Go標準ライブラリのioパッケージを利用し、基礎構造がバッファリングをサポートしていない場合でも、あらゆるio.Writerタイプのバッファリングされたレコードを提供するbufio.Writerを使用しました 。 これにより、バッファリングするバイト数を簡単に示すことができました。







次の課題は、バッファサイズとI / Oレイテンシの最適なトレードオフを決定することでした。 バッファが大きすぎるため、メモリを失う可能性がありますが、一方で、読み取り/書き込みに時間がかかりすぎるため、カメラからの着信ビデオの処理を停止します。 バッファーサイズを1KBから1MBに変更する簡単なベンチマークを実施し、3秒(または約1.8MB)のビデオをディスクに書き込むのに必要な時間を測定しました。









バッファサイズと記録時間







グラフに基づくと、約64KBの転換点がはっきりと見えます。これは、メモリをあまり使用せず、フレームを失わないように十分に高速である適切な選択です。 (この顕著な時間差は、フラッシュメモリの実装によって説明されます)。 このバッファの変更により、メモリ使用量がメガバイトのオーダーで削減されましたが、それでも目標の制限を下回っていません。









最終的なアーキテクチャ:書き込み前に常に64KBをバッファリングします







次のステップは、Go組み込みのpprofプロファイラーを使用して、サービスのメモリ使用量をプロファイリングすることでした。 実際、このプロセスはほとんど時間を費やしませんでしたが、ガベージコレクターに何か不審なことが起こりました。







ガベージコレクターのチューニング



Goのガベージコレクターは、 短い応答時間と単純さに依存しています。 彼にはチューニングのための唯一のパラメーターがあり、GOGCは合計ヒープサイズと利用可能なプロセスサイズの比率を制御するパーセンテージです。 このパラメーターを試しましたが、ガベージコレクション後に解放されたメモリがオペレーティングシステムにすぐに戻らないため、あまり効果はありませんでした。







Goのソースコードを分析し結果、ガベージコレクターは5分ごとにオペレーティングシステムにメモリの未使用ページのみを提供することがわかりました。 これにより、大きなバッファを作成および削除する際のメモリ割り当て/割り当て解除の一定のサイクルが回避されるため、応答時間に適しています。 しかし、私たちのように、消費されたメモリのサイズに敏感なアプリケーションでは、これは最良の選択肢ではありませんでした。 私たちのケースは応答時間にあまり敏感ではないので、メモリの使用量を減らすために短い応答時間を交換することをお勧めします。







オペレーティングシステムにメモリを返すためのタイムアウトは変更できませんが、Goにはこのためのdebug.FreeOSMemory関数があり、ガベージコレクションを開始し、強制メモリをオペレーティングシステムに返します。 便利でした。 5秒ごとにこの関数を呼び出すようにカメラを操作するサービスを変更し、RSSパラメーターがほぼ5倍減少して許容可能な10〜15 MBになることを確認しました。 もちろん、メモリ消費の削減は無料ではありませんが、リアルタイムの保証がなく、ガベージコレクションによる休止が頻繁に発生するため、応答時間をわずかに犠牲にする可能性があるため、このケースでは適切です。







これがなぜ役立つのか疑問に思っている場合:ビデオを定期的にクラウドにアップロードすると、約15 MBのメモリ消費のピークにつながります。 このようなピークが数秒間保持されると、安全に許可できますが、もう少し長くなります。 30MBのピークと200%のGOGC値は、ガベージコレクターが最大60MBを割り当てることができることを意味します。 ピーク後、Goは5分間メモリを返しませんが、debug.FreeOSMemoryを呼び出すことにより、この期間を5秒に短縮しました。







おわりに



ルーターが動作する新しい周辺機器を追加すると、メモリ制限に深刻な打撃を与えました。 メモリ使用量を削減するためにさまざまなバッファリング戦略を試してみましたが、最終的にこのGoガベージコレクタの構成がさまざまな動作をするのに役立ちました。 これは私たちにとって少し驚きでした。通常、Goで開発する場合、メモリの割り当てとガベージコレクションについては考えませんが、私たちの条件ではそれをしなければなりませんでした。 メモリ消費を5分の1に削減し、クラウドへのビデオアップロードをサポートしながら、ルーターに常に50 MBの空きRAMを確保することができました。








All Articles