RenderScript で Android 3.x でも Multi Thread 実行する方法が
わかりました。
以下その測定結果
最小実行時間 速度比 OS CPU
----------------------------------------------------------
Tegra 3 622ms x2.6 A3.2.1 Cortex-A9 1.3GHz x4
OMAP4460 1334ms x1.2 A4.0.1 Cortex-A9 1.2GHz x2
Tegra 2 1608ms x1.0 A3.1 Cortex-A9 1.0GHz x2
* 実行時間の数値が小さいほうが速い。
* 何度か実行して最速時間を抽出。
(Eee Pad TF201, Galaxy Nexus SC-04D, Optimus Pad L-06C)
やっと RenderScript で Quad core による効果を確認できるように
なりました。さすがに Tegra 3 の CPU は速いです。
前の記事で書いたとおり Android 3.x の HelloCompute サンプルは
シングルスレッドで実行されます。
マルチスレッド実行できない条件は下記の通り
RenderScript で自分自身のカーネルを呼び出す rsForEach() を記述する。
つまり下記のようなコードを用いて、rsForEach() が同じファイルの
root() 関数である (A) を呼び出すとだめです。
// RenderScript
void root( const uchar4 *v_in, uchar4 *v_out ) // -- (A)
{
~
}
rs_script script;
uchar4* in_vec;
uchar4* out_vec;
void filter()
{
rsForEach( script, rsGetAllocation( in_vec ), rsGetAllocation( out_vec ), 0 );
}
これを回避する方法は 2つあります。
●(1) Android 4.0 (API 14) 以降のみ
前回の記事に書いたとおり Java から直接 forEach_root() を使って
呼び出します。
Android 4.0 (API Level 14) 以降の HelloCompute はこれです。
同じ RenderScript 内の rsForEach() 呼び出しを通らないので実行が
スレッド並列化されます。
●(2) Android 3.0 (API 11) 以降対応
別の RenderScript から rsForEach() を使って呼び出します。
(2) の方法を使ったマルチスレッド実行できる RenderScript の例
(Android 3.2 Sample の HelloCompute を修正)
// RenderScript: mono.rs
#pragma version(1)
#pragma rs java_package_name(com.example.android.rs.hellocompute)
// 実際に実行したい関数
void root(const uchar4 *v_in, uchar4 *v_out, const void *usrData, uint32_t x, uint32_t y)
{
float4 f4 = rsUnpackColor8888(*v_in);
float3 mono = dot(f4.rgb, gMonoMult);
*v_out = rsPackColorTo8888(mono);
}
ダミーの RenderScript ファイルを作ります。call.rs
// RenderScript: call.rs
#pragma version(1)
#pragma rs java_package_name(com.example.android.rs.hellocompute)
void root(const uchar4 *v_in, uchar4 *v_out)
{ // ダミー関数。呼ばれない。
}
rs_script script;
uchar4* in_vec;
uchar4* out_vec;
void callscript()
{
rsForEach( script, rsGetAllocation( in_vec ), rsGetAllocation( out_vec ), 0 );
}
call.rs 経由で mono.rs を呼び出します。
// Java: HelloCompute.java
mScript = new ScriptC_mono(mRS, getResources(), R.raw.mono);
// 追加
mCall = new ScriptC_call(mRS, getResources(), R.raw.call);
mCall.bind_in_vec( mInAllocation );
mCall.bind_out_vec( mOutAllocation );
mCall.set_script( mScript );
// 削除
//mScript.set_gIn(mInAllocation);
//mScript.set_gOut(mOutAllocation);
//mScript.set_gScript(mScript);
//mScript.invoke_filter();
// call.rs 経由で呼び出す
mCall.invoke_callscript();
mOutAllocation.copyTo(mBitmapOut);
●マルチスレッド実行の検出
mono.rs にわざと競合が発生するような修正を加えます。
// RenderScript: mono.rs
static uint32_t sum= 0;
static uint32_t sum2= 0;
void root(const uchar4 *v_in, uchar4 *v_out)
{
uint32_t a= sum;
float4 f4 = rsUnpackColor8888(*v_in);
float3 mono = dot(f4.rgb, gMonoMult);
*v_out = rsPackColorTo8888(mono);
sum= a+1;
rsAtomicInc( &sum2 ); // Android 4.0 API 14 以降のみ
}
void dump_sum()
{
rsDebug( "sum=", sum );
rsDebug( "sum2=", sum2 );
}
実行後に Java から dump_sum() を呼び出します。
// Java: HelloCompute.java
mCall.invoke_callscript();
mOutAllocation.copyTo(mBitmapOut);
mScript.invoke_dump_sum(); // 結果表示
期待通り sum が毎回不定な値となり、並列実行されている証拠が
取れました。
Android 4.0 以降では rsAtomic 命令が使えます。
上の例の場合 sum2 は常に一定となり、呼ばれた回数 == 画素数
を示すようになりました。
同一ファイル内の rsForEach() では、カーネルコード実行後に
static な変数にアクセスできます。
上の例のように、競合する集約されたコードが記述されている可能性があり
互換性の問題があったのかもしれません。
forEach のエントリは常に root() で、それぞれ個別のファイルに記述する
必要があります。C言語でも独立性が保たれます。
並列実行しやすいようこのような仕様になっているものと考えられます。
関連エントリ
・Android 4.0 RenderScript と MultiCore CPU
・Android 3.x RenderScript (7) RenderScript Compute の速度
・Android 4.0 RenderScript Compute の速度 その2