Androidコードの高速化

アルゴリズムの最適化に関する前回の記事で始めた作業を続けます。 何が行われたか簡単に説明します。 既製のJavaソースとViola-Jonesアルゴリズムの実装の1つのカスケードモデルが採用されました。 このアルゴリズムは、写真内のオブジェクトの検索、特に顔の検索に使用されます。 携帯電話でテストを実施した結果、300 x 400の写真で元のJavaコードが54秒間機能することがわかりました。遅すぎたため、C ++で書き直したコードは14秒の結果を示しました。 コメントでは、次のようにC ++へのJava実装に追いつくことが提案されました。ボトルネックをプロファイリングして見つけ、2次元配列を1次元配列に置き換えます。 また、アルゴリズムをC ++で並列化する計画がありました。 すべてが行われ、調査されました。結果は以下のとおりです。



Javaリリースのプロファイルを作成します。 アルゴリズムの主要部分の動作時に統計を取得します。 このような画像が表示されますファイル内の結果は誰にとっても興味深いものです)。



リストメソッドは多くの時間を消費します。 この状況では、すべてのリストをC ++で使用される配列に簡単に置き換えることができます。 置換して、次の結果を取得します( ファイル内 ):



Math.sqrtに注意してください。実行の割合は6.7から13.5に上昇しました。つまり、他のユーザーが時間消費を削減し、電話での起動が38秒でした。 残りの時間は、配列要素とMath.sqrtの乗算、除算、取得というプリミティブメソッドに費やされます。 したがって、どこで何を変更できるかがわかりません。 C ++はコードを正確に繰り返します。



2次元配列ではなく1次元配列 。 次のコードを変更します。

int[][] grayImage=new int[width][height]; int[][] squares=new int[width][height];
      
      





に:

  int[] grayImage=new int[width * height]; int[] squares=new int[width * height];
      
      





そして、適切な使用場所で:

  int total_x = grayImage[(i + w) * height + j + h] + grayImage[i * height + j] - grayImage[i * height + j + h] - grayImage[(i + w) * height + j]; int total_x2 = squares[(i + w) * height + j + h] + squares[i * height + j] - squares[i * height + j + h] - squares[(i + w) * height + j]; ... rect_sum += (int) ((grayImage[rx2 * height + ry2] - grayImage[rx1 * height + ry2] - grayImage[rx2 * height + ry1] + grayImage[rx1 * height + ry1]) * r.weight); }
      
      





結果はまったく同じです。 乗算操作の数を減らして、配列の要素を計算してみましょう。 これをしましょう:

  int iwh = (i + w) * height; int ih = i * height; int total_x = grayImage[iwh + j + h] + grayImage[ih + j] - grayImage[ih + j + h] - grayImage[iwh + j]; int total_x2 = squares[iwh + j + h] + squares[ih + j] - squares[ih + j + h] - squares[iwh + j]; ... int rx2h = rx2 * height; int rx1h = rx1 * height; rect_sum += (int) ((grayImage[rx2h + ry2] - grayImage[rx1h + ry2] - grayImage[rx2h + ry1] + grayImage[rx1h + ry1]) * r.weight);
      
      





結果は変更されていません。 1次元配列はプラスではなく、コードの可読性のマイナスのみでした。

したがって、Javaを使用すると、C ++では38秒でしたが14秒でした。



並列化。 クアッドコアプロセッサを搭載したGT-I9300I電話で、C ++で記述されたアルゴリズムを並列化してみましょう。 Androidでは、ドキュメントに書かれているようにposixスレッドを使用できますが、わずかにトリミングされていますが、スレッドを作成し、パラメーターを渡し、実行を待つだけです。 これはすべて単純に行われます。

 #include <pthread.h> ... extern "C" { struct ThreadArgument { void *array; int threadNum; }; void *worker_thread(void *arg) { ThreadArgument *arg1 = (ThreadArgument*) arg; ((Detector*) (arg1->array))->workerTansient(arg1->threadNum); pthread_exit(NULL); } } ... pthread_t m_pt[threadsNum - 1]; if (threadsNum > 1) { res2 = new VectorRects*[threadsNum - 1]; for (int l = 0; l < threadsNum - 1; l++) { ThreadArgument* args = new ThreadArgument; args->array = (void*)this; args->threadNum = l + 1; int success = pthread_create(&m_pt[l], NULL, worker_thread, args); if (success == 0) { __android_log_print(ANDROID_LOG_INFO, "Detector", "thread %d started", l); } } } __android_log_print(ANDROID_LOG_INFO, "Detector", "gettFaces3 baseScale=%f maxScale=%f scale_inc=%f", baseScale, maxScale, scale_inc); ret = getResult(0); if (threadsNum > 1) { for (int l = 0; l < threadsNum - 1; l++) { int success = pthread_join(m_pt[l], NULL); for (int b = 0; b < res2[l]->currIndex; b++) { ret->addRect(res2[l]->rects[b]); } } } ... VectorRects* Detector::workerTansient(int threadNum) { __android_log_print(ANDROID_LOG_INFO, "Detector", "workerTansient thread running %d", threadNum); res2[threadNum - 1] = getResult(threadNum); return NULL; } ...
      
      





次の結果が得られます。1つのスレッドで14秒、2つのスレッドで8秒、3つのスレッドで5秒、4つ以上のスレッドで4秒。 シングルコアプロセッサを搭載したNexus S仮想デバイスでは、並列化によって何も起こりませんでした。



入手したアルゴリズムのソースは、 ここからダウンロードできます 。 次のように使用します。

  Detector detector = Detector.create(inputHaas); List<Rectangle> res = detector.getFaces(image, 1.2f, 1.1f, .05f, 2, true, useCpp, threadsNum);
      
      





ここで、inputHaasはモデルストリーム、つまり 元のアルゴリズムのファイルhaarcascade_frontalface_default.xml、useCpp-C ++を使用するかどうか、threadsNum-C ++コードのスレッド数。 検出器は一度トリガーされると想定されます。



合計 反復で使用されるLinkedListを全体として配列に置き換えると、現在のアルゴリズムでは1.5倍の増加が見られましたが、C ++での実装には2.5倍遅れています。 2次元配列から1次元配列への変換では、変更はありませんでした。 C ++での並列化を使用して、クアッドコアプロセッサでプロセスを3倍高速化することができました。これは、すでにはるかに優れていますが、理想からはほど遠いものです。 RenderScriptに目を向けることができます。これは、ネイティブレベルでの複雑な計算のメカニズムです。



All Articles