組み込みLinuxオペレーティングシステム用のOpenCVベースのプログラムの最適化[閉じる]

組み込みLinuxオペレーティングシステム用のOpenCVベースのプログラムの最適化[閉じる]

私はBuildrootを使用してRaspberry PI3用の組み込みLinuxオペレーティングシステムを構築しています。オペレーティングシステムは複数のアプリケーションを処理するために使用され、そのうちの1つはOpenCV(v3.3.0)に基づいてオブジェクト検出を実行します。

Raspbian Jessy + Pythonを使い始めましたが、簡単な例を実行するのに時間がかかることがわかり、Pythonの代わりに最適化された機能+ C ++開発で独自のRTOSを設計することにしました。

この最適化により、4コアRPI + 1GB RAMがこれらのアプリケーションを処理できると思います。問題は、これにもかかわらず、最も簡単なコンピュータビジョンプログラムが時間がかかるということです。

PCとラズベリーPI3の比較

これは、プログラムの各部分の実行時間のサイズについてのアイデアを得るために書いた単純なプログラムです。

#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;
}

i7 PCでの実行結果 ここに画像の説明を入力してください。

Raspberry PIの実行結果(Buildrootで生成されたオペレーティングシステム) ここに画像の説明を入力してください。

ご覧のとおり、大きな違いがあります。私にとって必要なのは、すべての詳細を最適化することです。処理ステップはリアルタイムで「ほぼ」発生します。最大15ms時間

私の質問は次のとおりです

  • 集中的なコンピューティングアプリケーションを処理するためにオペレーティングシステムを最適化し、各部分の優先順位をどのように制御できますか?
  • 需要を満たすためにRPI3の4つのコアを最大限に活用する方法は?
  • OpenCVに加えて他の可能性はありますか?
  • C ++の代わりにCを使用する必要がありますか?
  • 提案されたハードウェアの改善はありますか?

答え1

のための:

集中的なコンピューティングアプリケーションを処理するためにオペレーティングシステムを最適化し、各部分の優先順位をどのように制御できますか?

一般的な最適化の場合、実際に必要なものだけがバックグラウンドで実行されていることを確認するなど、一般的な作業に加えてOS側で実行できる作業はあまりありません。元のPiでは、これらの機能のアセンブリ最適化バージョンを提供するmemmove()「cofi」と呼ばれるライブラリを使用すると、同様の機能を高速化できますが、LD_PRELOADPi 3では役に立つかどうかはわかりません。

優先順位については、実際にはマニュアルページを見なければなりませんが、並列化しない限り、通常はそうすることはできません。 (あなたの場合、確実な解決策はプロセスを取得し、IPCを使用するときに各ステップを実行するようです(おそらくパフォーマンス上の理由で共有メモリ)間でデータを移動します。

テストプログラムで引用された結果によれば、特に処理と保存のステップはPiで約10倍遅いのに対し、アクセスステップは約5倍だけ遅く、この数値はPi 3の使用と似ています。汎用PCと比較した場合、おおよその推定値よりも少ない。 PiのCPUは、PCテストを実行しているCPUよりもかなり遅くなります。 (並列化をまったくしないと、ほとんどの最新のx86 CPUが全ロードで他のCPUよりも高速に実行できるため、ギャップが広がります。ロード全体でコアがはるかに高速になります)、これは影響を与えます。 ARM ISAはx86 ISAとは大きく異なります(ARMはx86と比較して1サイクルあたり少ない操作を実行しますが、通常RAMに頻繁にアクセスする必要はなく、x86ほど高価な分岐予測不足は発生しません)。 GCCはPC上でタスクをソートする方法に最適化されており、Piでは最適に機能しません。

また、どのカメラを使用しているかはわかりませんが、処理中の画像の解像度を下げるとより良い時間が得られることを望み、圧縮を避けることで取得時間を短縮することができます。形式(損失圧縮を使用しない場合、解像度はもはや重要ではないことを意味します)

需要を満たすためにRPI3の4つのコアを最大限に活用する方法は?

独自のコードで並列化してください。カーネルでSMPが有効になっていることを確認してから(RPi財団の公式設定を使用している場合は有効にする必要があります)、並列に実行してください。 OpenCV自体がどのように並列化を実行するかはわかりませんが、OpenMP(互いに依存しないループで反復を並列化するための非常に簡単な方法を提供します)を見てください。

OpenCVに加えて他の可能性はありますか?

あるかもしれませんが、誰もがOpenCVで標準化しているので、それを使用することをお勧めします(誰もがOpenCVを使用しているので、実装の技術的な助けを借りるのは簡単です)。

C ++の代わりにCを使用する必要がありますか?

物をどのように使用するかによって異なります。 CよりもC ++で遅いコードを書く方がはるかに簡単ですが、両方の言語でクイックコードを書くことは難しくありません。 2つの言語の多くの最適化技術は非常によく似ています(たとえば、malloc()重要なセクションで呼び出さないように、起動時にすべてを事前に割り当てたり、呼び出しを避けたりstat())。特にC ++では、どこでも呼び出され、結果として非常に遅くなるstd::string問題を避けてください(場合によっては、Cスタイルの文字列に変換してパフォーマンスが40%以上向上するのをmalloc()見ました)。std::string

提案されたハードウェアの改善はありますか?

ハードウェアコストを低く保ち、スペースが限られていると仮定すると(したがってRaspberry Pi)、私は本当にそれを考えることはできません。 Pi(すべての繰り返し)は、この価格帯のコンピュータビジョン作業に非常に適したSoCを使用します。もう少し大きくて高価な製品を使用したい場合は、NVIDIA Jetsonボードをお勧めします(192 CUDAコアが統合されたQuadroクラスのGPUを搭載したTegra SoCを使用しているため、実行可能かもしれません)。処理作業量は速くなりますが、Buildrootを操作することはPiよりはるかに複雑です。

コメントに応じて編集:

プロセスレベルの並列化はマルチスレッドとは異なります(最も大きな違いはリソースが共有される方法です。デフォルトでは、スレッドはすべてを共有し、プロセスは何も共有しません)。通常、スループットが高い場合は、スレッドセーフを心配することなく効率的なコードを記述する方が簡単であるため、プロセスベースの並列化を使用する方が一般的です。

オプションに関して言及した2つはシステムパフォーマンスに大きな影響を与える可能性がありますが、最終的にスループットとレイテンシのバランスを保ちます。プリエンプションモデルは、カーネルモードで実行されるもの(システムコールなど)が再スケジュールされる方法を制御します。 3つのオプションがあります。

  1. プリエンプションなし:これは、カーネルモードで実行されているすべてのジョブを中断できないことを意味します。これは、SVR4および4.4BSDの動作だけでなく、他のほとんどの以前のUNIXシステムの動作方法とも一致します。スループットにはとても良いですが、待ち時間には非常に悪いです。したがって、通常、CPU が多い大規模サーバーでのみ使用されます。 CPU が多いほど、プリエンプトできるジョブを実行する可能性が高くなります。
  2. 自発的なプリエンプション:これにより、カーネルの各機能を再スケジュールできる場所を定義できます。これは、スループットと待ち時間の間の適切なバランスを提供するため、ほとんどのデスクトップ指向のLinuxディストリビューションで使用される設定です。
  3. 完全プリエンプション:これは、カーネルの(ほぼ)すべてのコードがいつでも(ほぼ)中断される可能性があることを意味します。これは、リアルタイムマルチメディア操作に使用されるシステムなど、非常に低い入力および外部イベント待ち時間を必要とするシステムに役立ちます。これはスループットの面では絶対に良いではありませんが、待ち時間に勝つことはできません。

これと比較して、タイマー周波数は解釈する方が簡単です。実行待機中の他のプログラムがある場合は、一部のプログラムが中断なく実行できる最大時間を制御します。値が高いほど時間が短くなり(待機時間が短くなり、スループットが低くなり)、値が低いほど時間が長くなります(待機時間が長くなり、スループットが高くなります)。通常、プリエンプションモデルを自発的に設定し、タイマー周波数を300 Hzに設定してから、タイマー周波数を最初に変更することをお勧めします(通常これはより顕著な影響を与えるためです)。

Movidius NCSの場合、その価値があるかどうかは、処理する必要があるデータの量によって異なります。これはUSB接続の帯域幅によって制限されるためです(PiにはUSB 2.0コントローラのみがあるため、100%未満に制限されるだけでなく、Movidiusが設計された帯域幅の10分の1に対応するため、少なくともEthernetアダプタとバスを共有する必要があるため、待ち時間スループットが低下します。32ビットカラーで1920 x 1080の単一フレームを低速で処理する場合は可能ですが、同じビデオをフルフレームレートでストリーミングする必要がある場合は、待ち時間の問題が発生する可能性があります。 、電源が供給されているハブがあることを確認してください。

関連情報