Buildrootを使用して、RaspberryPi3用の独自の組み込みLinuxOSを構築しています。このOSは、いくつかのアプリケーションを処理するために使用され、そのうちの1つはOpenCV(v3.3.0)に基づいてオブジェクト検出を実行します。
Raspbian Jessy + Pythonから始めましたが、簡単な例を実行するのに時間がかかることがわかったので、独自のRTOS Pythonの代わりに最適化された機能とC++開発を使用します。
これらの最適化により、RPIの4つのコア+ 1GB RAMがそのようなアプリケーションを処理できると思いました。問題は、これらのことでも、最も単純なコンピュータービジョンプログラムに時間がかかることです。
これは、プログラムの各部分の実行時間の大きさのオーダーを理解するために私が書いた単純なプログラムです。
#include <stdio.h>
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <time.h> /* clock_t, clock, CLOCKS_PER_SEC */
using namespace cv;
using namespace std;
int main()
{
setUseOptimized(true);
clock_t t_access, t_proc, t_save, t_total;
// Access time.
t_access = clock();
Mat img0 = imread("img0.jpg", IMREAD_COLOR);// takes ~90ms
t_access = clock() - t_access;
// Processing time
t_proc = clock();
cvtColor(img0, img0, CV_BGR2GRAY);
blur(img0, img0, Size(9,9));// takes ~18ms
t_proc = clock() - t_proc;
// Saving time
t_save = clock();
imwrite("img1.jpg", img0);
t_save = clock() - t_save;
t_total = t_access + t_proc + t_save;
//printf("CLOCKS_PER_SEC = %d\n\n", CLOCKS_PER_SEC);
printf("(TEST 0) Total execution time\t %d cycles \t= %f ms!\n", t_total,((float)t_total)*1000./CLOCKS_PER_SEC);
printf("---->> Accessing in\t %d cycles \t= %f ms.\n", t_access,((float)t_access)*1000./CLOCKS_PER_SEC);
printf("---->> Processing in\t %d cycles \t= %f ms.\n", t_proc,((float)t_proc)*1000./CLOCKS_PER_SEC);
printf("---->> Saving in\t %d cycles \t= %f ms.\n", t_save,((float)t_save)*1000./CLOCKS_PER_SEC);
return 0;
}
Raspberry Pi(Buildrootから生成されたOS)での実行結果
ご覧のとおり、大きな違いがあります。私が必要としているのは、この例の処理ステップがで「ほぼ」リアルタイムで発生するように、すべての詳細を最適化することです。 )最大15msの時間。
私の質問は:
順番に:
集中的な計算アプリケーションを処理できるようにOSを最適化するにはどうすればよいですか?また、各部分の優先順位を制御するにはどうすればよいですか?
一般的な最適化の場合、バックグラウンドで実際に実行する必要があるものだけを持っていることを確認するなど、通常のもの以外にOS側でできることはあまりありません。元のPiでは、これらの関数のアセンブリ最適化バージョンを提供する 'cofi'というライブラリをLD_PRELOAD
'することで、memmove()
および同様の関数を高速化できますが、それが役立つかどうかはわかりません。 Pi3。
優先順位付けについては、これは実際にmanページを確認するためのものですが、通常、並列化しない限り実行できません(あなたの場合、プロセスを勝ち取った各ステップを実行してIPC(おそらくパフォーマンス上の理由で共有メモリ)それらの間でデータを移動します)。
テストプログラムから引用した結果に注意してください。特に、処理と保存の手順はどちらもPiでは約10倍遅く、アクセス手順は約5倍遅く、これらの数値はPi3を1年未満の一般的なPCと比較したときに私が期待するものの概算。 PiのCPUは、PCテストを実行したものよりもほぼ確実に大幅に低速です(並列化をまったく行わなかった場合、最新のx86 CPUは、で単一コアを単独で実行できるため、ギャップはさらに拡大します。全負荷ですべてのコアを実行できるよりもはるかに高速で全負荷)、それは影響を及ぼします。 ARM ISAもx86ISAとは大幅に異なります(ARMはx86と比較してサイクルあたりの処理が少ない傾向がありますが、通常はアクセスする必要はありませんRAMは、x86ほど頻繁に、通常は分岐予測ミスを発生させません)。そのため、GCCがPCで物事を配置する方法に最適化されたコードは、Piでは最適ではありません。
使用しているカメラもわかりませんが、処理している画像の解像度を下げることでより良い時間を得ることができ、使用を避ければ取得時間を短縮できると思います。圧縮形式(非可逆圧縮を使用しないということは、解像度がそれほど重要ではないことを意味します)。
RPI3の4つのコアを完全に使用して要件を満たすにはどうすればよいですか?
独自のコードでの並列化。カーネルでSMPが有効になっていることを確認してから(RPi Foundationの公式構成を使用している場合は有効にする必要があります)、並行して実行してみてください。 OpenCVが物事自体を並列化するためにどれだけのことをするかはわかりませんが、OpenMPも検討することをお勧めします(相互依存しないループで反復を並列化するための合理的に簡単な方法を提供します)。
OpenCVの代わりに他の可能性はありますか?
あるかもしれませんが、誰もがOpenCVで標準化されているので、それを使用することをお勧めします(誰もがそれを使用するので、実装に関する技術的な支援を得るのが簡単になります)。
C++の代わりにCを使用する必要がありますか?
それはあなたが物事をどのように使っているかに依存します。 CよりもC++でスローコードを書く方がはるかに簡単ですが、どちらの言語でもスローコードを書くことはそれほど難しくありません。最適化手法の多くは、両方の言語で非常に似ています(たとえば、起動時にすべてを事前に割り当てて、クリティカルセクションでmalloc()
を呼び出さないようにしたり、stat()
を呼び出さないようにします)。 。ただし、特にC++の場合は、ペストのようにstd::string
を避け、至る所でmalloc()
を呼び出すため、結果としてめちゃくちゃ遅くなります(std::string
からCスタイルの文字列に切り替わる変換を見てきました)場合によっては、パフォーマンスが40%以上向上します)。
おすすめのハードウェアの改善はありますか?
ハードウェアのコストを低く抑えようとしていて、スペースに制約がある(したがって、Raspberry Piの選択)という仮定の下では、私が考えることのできるものは実際にはありません。 Pi(すべての反復で)は、その価格帯でのコンピュータービジョン作業に非常にユニークに適したSoCを使用します。もう少し大きく、やや高価なものを使用したい場合は、NVIDIA Jetsonボードをお勧めします(192個のCUDAコアと統合されたQuadroと同等のGPUを備えたTegra SoCを使用しているため、おそらく処理を実行できますワークロードははるかに高速です)が、Buildrootをそこで動作させることは、Piよりもはるかに複雑です。
コメントに応じて編集:
プロセスレベルでの並列化はマルチスレッドと同じではなく、大幅に異なります(最大の違いは、リソースの共有方法です。デフォルトでは、スレッドはすべてを共有し、プロセスは何も共有しません)。一般に、多くの処理が含まれる場合は、スレッドの安全性を気にすることなく効率的なコードを記述しやすいため、(通常は)プロセスベースの並列化を使用する方が適切です。
オプションに関しては、あなたが言及した2つはシステムのパフォーマンスに大きな影響を与える可能性がありますが、どちらもスループットと遅延の間のトレードオフになります。プリエンプションモデルは、カーネルモードで実行されているもの(syscallなど)を再スケジュールする方法を制御します。 3つのオプションがあります:
対照的に、タイマーの周波数は説明がはるかに簡単です。実行を待機している他の何かがある場合に、何かが中断されることなく実行できる最長の期間を制御します。値が大きいほど期間が短くなり(待ち時間が短くなり、スループットが低くなります)、値が小さいほど期間が長くなります(待ち時間が長くなり、スループットが高くなります)。一般的な開始点として、プリエンプションモデルを任意に設定し、タイマー周波数を300 Hzに設定してから、最初にタイマー周波数を変更して実験を開始することをお勧めします(通常はより目に見える影響があります)。
Movidius NCSの場合、それが価値があるかどうかは、USB接続によって帯域幅が制限されるため、処理する必要のあるデータの量によって異なります(PiにはUSB 2.0コントローラーが1つしかないため、 Movidiusが設計されている帯域幅の10分の1未満の場合は、少なくともイーサネットアダプターとバスを共有する必要があります。これにより、遅延とスループットの両方が損なわれます)。 1920x1080のシングルフレームを32ビットカラーで低レートで実行している場合は実行可能かもしれませんが、同じビデオのストリーミング処理をフルフレームレートで実行する必要がある場合は、おそらく遅延が発生します問題。使用する場合は、必ず電源付きハブを入手してください(そうしないと、Piが提供できる以上の電力を引き出そうとして問題が発生する可能性があります)。