インテル® Atom™ プロセッサー・ベースのプラットフォーム上の Android* メディア・アプリケーションでインテル® SSE 命令を使用する利点

同カテゴリーの次の記事

ケーススタディー: Escapist Games* がインテル® プラットフォーム・ベースの Windows* 8 および Android* で Star Chart* のユーザー・エクスペリエンスを向上

この記事は、インテル® デベロッパー・ゾーンに掲載されている「Finer Points of using SSE Instructions for Android* media apps on the Intel® Atom™ Platform」の日本語参考訳です。


概要

スマートフォンやタブレットなどのハンドヘルド・デバイスの設計において、電力効率はこれまでも、そして今後も重要な要素と言えるでしょう。デバイス性能の進化に伴い、ハンドヘルド・デバイスは、電池を絶え間なく消耗するエンターテインメント (音楽や動画の長時間視聴) に利用されることが多くなりました。長時間のフライト (特に座席にエンターテインメント・システムがない古い飛行機) では、乗客がハンドヘルド・デバイスを使用している光景をよく目にします。最新の Samsung* Galaxy Tab* 3 を含むいくつかの Android* ベースのスマートフォンやタブレットで採用されているインテル® Atom™ プロセッサーは、メディアコーデックに対し効率良いハードウェアによるサポートを提供しています。しかし、オーディオを出力デバイスへ送る前に独自な後処理効果を実行する場合など、一部のベンダーではまだソフトウェアによる実装が行われています。この場合、メディア処理を高速に行うには、インテル® ストリーミング SIMD 拡張命令 (インテル® SSE) によるソフトウェアの最適化が不可欠です。インテル® SSE 命令を使用することで、CPU の “スリープ” 状態になる頻度が増え、バッテリーの寿命が延びます。この記事では、インテル® SSE の実装に加えて、インテル® Atom™ プロセッサーでインテル® SSE コードのパフォーマンスを大幅に向上する効果的な最適化についても述べます。これらのガイドラインに従うことで、18 時間にわたるニューヨークからシンガポールへの長いフライトでも、ユーザーはデバイスを長時間にわたって利用し、充実したエクスペリエンスを得られるようになるでしょう。

はじめに

2012 年にインテルは、インテル® Atom™ プロセッサーを採用した初の Android* ベースのスマートフォンである LAVA* をリリースしました。それ以来、多くのデバイスメーカー (OEM/ODM) がこれに追随し、メディア処理フレームワークを ARM からインテルの x86 アーキテクチャーに移行する必要がありました。

Android* NDK を使用してインテル® Atom™ プロセッサー向けのネイティブ Android* アプリをビルドする方法を示したチュートリアルはいくつかあります (例えば、「インテル® Atom™ プロセッサー・ベースのプラットフォームにおける Android* アプリケーションの開発と最適化」 など)。

この記事では、既存の ARM NEON* のベクトル化アルゴリズムを、インテル® Atom™ プロセッサーで実行可能なインテル® SSE コードへ移植する方法を説明しています。また、多くの場合、単にインテル® SSE を実装するだけより、命令レベルの調整を行ったほうがより優れたパフォーマンスが得られます。ここでは、リファレンス MP3 デコーダーとほかのメディア・アルゴリズムのチューニングの実例を基に、インテル® Atom™ プロセッサーでインテル® SSE のパフォーマンスをさらに向上するためのヒントを述べます。

インテル® SSE 命令

最初に、SIMD 命令について簡単に説明しましょう。SIMD 命令は、1996 年にインテル® MMX® テクノロジーとして初めてインテル® アーキテクチャーに導入されました。インテル® MMX® テクノロジーは、バイト、ワード、ダブルワードのパックド整数データ型の SIMD 操作を提供します。インテル® Pentium® III プロセッサーでインテル® ストリーミング SIMD 拡張命令 (インテル® SSE) が導入され、SIMD 操作は 4 つのパックド単精度浮動小数点値のオペランドを処理できるように拡張されました。

図 1 に一般的な SIMD 操作を示します。4 つのパックドデータ要素 2 つ (X1、X2、X3、X4 と Y1、Y2、Y3、Y4) が並列処理され、各データ要素ペア (X1 と Y1、X2 と Y3、X3 と Y3、X4 と Y4) ごとに同じ操作が行われます。4 つの並列処理の結果は、4 つのパックドデータ要素として提供されます。


図 1. 一般的な SIMD 操作
(出典: 『インテル® 64 アーキテクチャーおよび IA-32 アーキテクチャー
最適化リファレンス・マニュアル』 Intel Corporation、2013 年)

インテル® Pentium® 4 プロセッサーでは、インテル® ストリーミング SIMD 拡張命令 2 (インテル® SSE2) とインテル® ストリーミング SIMD 拡張命令 3 (インテル® SSE3) が導入され、SIMD 操作はさらに拡張されました。そして、インテル® Xeon® プロセッサー 5100 番台では、インテル® ストリーミング SIMD 拡張命令 3 補足命令(インテル® SSSE3) が導入されました。第 2 世代インテル® Atom™ プロセッサーは、インテル® SSSE3 までサポートしています (図 2 を参照)。訳者注: 2013 年 9 月に発表された Silvermont (開発コード名) アーキテクチャー・ベースの第 3 世代インテル® Atom™ プロセッサーは、インテル® SSE4.2 までサポートしています。

SIMD 拡張は、次の点を除いて、IA-32 アーキテクチャーとインテル® 64 アーキテクチャーで同じように動作します。

  • XMM レジスターを使用する 128 ビット SIMD 命令は、16 個の XMM レジスターに 64 ビット・モードでアクセスできます。32 ビット・モードでは、8 個の XMM レジスターのみ利用できます。
  • 32 ビット・モードでは、命令は 32 ビット幅の 8 個の汎用レジスターにアクセスできます。64 ビット・モードでは、64 ビット幅の 16 個の汎用レジスターを利用できます。


図 2. 第 2 世代インテル® Atom™ プロセッサーはインテル® SSSE3 までサポート

マイクロアーキテクチャーに関する特性

ブロックされたストアフォワード

“ブロックされたストアフォワード” とは、メモリーへのストアを、最近ストアされたデータの一部にアクセスする後続のロードにフォワードできないコードシーケンスを指します。これは、データがプロセッサー・キャッシュに書き込まれ、ロードする部分がリロードされるまで、数サイクルにわたってロードをストールします。『インテル® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアル』の第 2 章に、インテル® Core™ マイクロアーキテクチャーでフォワードがある場合とない場合の各ケースの詳細な図が掲載されています。

第 2 世代インテル® Atom™ プロセッサーでは、ストールなしで効率良くデータをフォワードできるケースは限られています。ロードとストアが同じアドレスにアクセスし、オペランドのサイズが同じで、汎用レジスター (eax、ebx など) からの場合のみ可能です。インテル® SSE オペランド (xmm レジスターから) のストアは、後続のロードにフォワードされないため行うべきではありません。通常、レジスター内でデータが操作され、ストールを引き起こすことなく処理を続行することができます。

MP3 デコーダーの例では、“ウィンドウイング” ループが周波数サンプルとウィンドウ係数の乗算結果を集計するサブバンド合成フィルターでこの問題が発生しました。インテル® SSE により最適化されたルーチンは、ADDPS 命令を用いて 1 つの xmm レジスターで 4 つの中間合計値を計算します。そして、この 4 つの値をレジスターの水平方向に合計して計算を完了します。可能性のある実装方法の 1 つとしては、インテル® SSE の 16 バイトのパックドオペランドをメモリーに格納し、メモリーから 4 つの要素を合計します。

// windowing code

__m128 sum4;
float pSum[4];

sum4 = _mm_mul_ps(_mm_load_ps(&window[0], &b0[0]);
sum4 = _mm_add_ps(sum4,
    _mm_mul_ps(_mm_load_ps(&window[4], &b0[4]));
...			

_mm_store_ps (pSum, sum4);	// sum3 sum2 sum1 sum0
int pcm_sample =
    (int)(pSum[0]+pSum[1]+pSum[2]+pSum[3]);

pcm_out[i] = CLIP(pcm_sample);

この場合、pSum 配列への 16 バイトのストアと後続の pSum からの 4 バイトのロードで、ブロックされたストアフォワードによりストールが発生します。これは、xmm レジスターで水平方向に合計を計算するバージョンを記述することで回避できます。

// horizontal add of elements from sum4
	// sum4 =  sum4[0]+sum4[1]+sum4[2]+sum4[3];
	sum4 = _mm_hadd_ps( sum4, sum4);  // --- --- sum3+sum2 sum1+sum0
	sum4 = _mm_hadd_ps( sum4, sum4);  // --- --- -------- sum3+2+1+0

	//	Clipping to 16-bit integer range
	sum4 = _mm_max_ss(sum4, lowerBound4);
	sum4 = _mm_min_ss(sum4, upperBound4);		

//	Convert to integer and store to pcm buffer
	pcm_out[i] = _mm_cvtt_ss2si(sum4);

水平方向の合計は、HADDPS 命令か (上記のコード例を参照)、一連の加算とシャッフルにより行います。HADDPS 命令は、第 2 世代インテル® Atom™ プロセッサーでは高速ですが、インテル® Core™ マイクロアーキテクチャーの多くでは遅くなります。ただし、いずれも、ストアフォワードの問題があるコードよりは高速に実行できます。

また、16 ビットを超えるサンプルのクリッピングにインテル® SSE の min 命令と max 命令を利用できます。リファレンス・デコーダーの例では、これらの変更をすべて行ったところ、インテル® SSE により最適化されたサブバンド合成フィルターでパフォーマンスが 15% 向上しました。

アライメントされていないロード

インテル® SSE ルーチンのパフォーマンスを最大限に引き出すには、メモリー・アライメントが重要です。インテル® Core™ マイクロアーキテクチャーでは、アライメントされていないロード (とそれによって生じるキャッシュラインの分割) への対応が進んでいますが、インテル® Atom™ プロセッサーではアライメントにより大幅なパフォーマンスの向上が得られます。いずれも、アライメントにより効果がもたらされますが、特にインテル® Atom™ プロセッサーでは大きな効果が得られます。

ほとんどのインテル® SSE コードでは、XMM レジスターからロードする際にアライメントされた移動 (MOVAPS や MOVNTA) を活用できるように、データ構造を 16 バイト境界でアライメントするようにします。図 3 は、3 次元座標のセットが 4 つ格納された “ハイブリッド配列構造体” の例です。構造体が 16 バイトでアライメントされていることを保証できれば、MOVAPS 命令を使って 4 つの座標のセットにアクセスできます。


図 3. 16 バイトでアライメントされたデータのロード

プログラマーは、データの割り当て方法 (静的か動的か) に応じて、いくつかの手法によりアライメントを制御できます (図 4 を参照)。

        __declspec(align(16)) float a[N];               // 静的または自動
        _MM_ALIGN16 float a[N];                         // 静的にアライメントするコンパイラー・マクロ
        int* b = _mm_malloc(N * sizeof(int), 16);  // 動的割り当て
        F32vec4 c, d[N / 4];                                // ベクトルクラスは常にアライメントされる

図 4. 静的および動的データ構造体のアライメント手法

インテル® SSE アルゴリズムに本質的にアライメントされていないアクセスパターンがある場合は、より複雑な制御が必要になります。サブバンド合成の “ウィンドウイング” ルーチンのチューニングでは、この問題にも直面しました。ウィンドウイング係数バッファーは 16 バイトでアライメント可能ですが、バッファーへのポインターは、コードの各パスでアライメントが変わるような循環的な方法でインクリメントされます。これらは 4 つの 32 ビット float 値なので、16 バイト境界ではオフセットは 0、4、8、または 12 バイトになります。

この問題に対処するため、いくつかの方法を検討しました。1 つ目は、すべてにアライメントされていない移動 (MOVUPS) を使用し、パフォーマンス・ペナルティーを受け入れることです。2 つ目は、アライメントされているか (オフセットがゼロか) を確認し、その結果に応じて MOVAPS または MOVUPS を使用します。これにより、時間が 25% 短縮されます。

最も高速な (そして、やや複雑な) 方法は、2 つの XMM レジスターを結合し指定したバイト数分シフトしてオペランドを取得する PALIGNR 命令を使用します。この方法では、4 つのオフセットすべてに対応するため、別のコードパスを記述する必要があります。図 5 のケースでは、オフセットが 8 バイトであることが分かっているため、対象データ周辺のアライメントされた場所から連続する 16 バイトのチャンクをロードし、PALIGNR (実際には _mm_alignr_epi8 intrinsic) により必要な値にシフトします。


図 5. PALIGNR によりキャッシュラインの分割を引き起こすロードを回避

これは、皆さんが思っていたよりも複雑な作業かもしれません。しかし、適度な作業量で実装でき、オリジナルのインテル® SSE ルーチンよりもパフォーマンスをさらに15% も向上することができます。

部分的なレジスター依存性

一部の命令 (MOVLPS、MOVHPS、PINSRW など) は、XMM レジスターの一部にのみロードします。そのため、操作を完了するには、マシンがレジスターの以前の内容を把握しておく必要があります。次のように、XOR を使用したり、レジスターからそのレジスターを引く除算を行って、プログラマーがあらかじめレジスター全体をゼロに設定しておくことで、偽の依存性を排除できます。

PXOR xmm0, xmm0
PSUBD xmm0, xmm0

実行パイプラインは、依存性をなくすためのものとしてこれらを解釈するため、後続のレジスターへの部分アクセスでストールは発生しません。つまり、レジスターで以前の命令との間にリードアフターライト (RAW) 依存がある場合でも、以前の値に関係なくすべてのビットがゼロに設定されるため、マシンはその確認を行う必要がありません。

レイテンシーの長い命令

インテル® SSE アルゴリズムを実装する際は、使用する各命令のレイテンシー/スループットに関して『インテル® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアル』を確認することで、パフォーマンスの向上に役立つ情報が得られるでしょう。特に、除算と平方根演算は、処理を繰り返して結果を計算するため 30 サイクル以上かかります。これらの操作は、テーブルルックアップ、逆数近似 (RCPPS 命令)、あるいはニュートン・ラプソン・シーケンスに置き換えることを検討してください。

MP3 デコーダーの例では、逆量子化フェーズの計算ステップで値のセット (すべて 2 の累乗) による除算が必要です。整数コードでは、これは高速な右シフトに変換できます。しかし、x86 アルゴリズムは浮動小数点表現を使用しているため、このコードは完全な精度で除算を行います。例では、除算をあらかじめ計算された逆数による乗算に置き換えました (y = x / 8.0 の代わりに y = x * 0.125 を使用)。これにより、どちらのデコーダーでもパフォーマンスが 5% 向上しました。簡単な変更にしては悪くない結果と言えるでしょう。

ストリーミング・ストア

最後のヒントは、メモリーの最適化に関連するものです。インテル® Core™ マイクロアーキテクチャーとは異なり、インテル® Atom™ プロセッサーには L3 キャッシュがありません。L2 キャッシュは通常 512KB だけなので、データセットが収まるかどうかを確認してください。収まらない場合は、キャッシュにアクセスせずメインメモリーに直接書き込むストリーミング・ストア (一時的でないストア命令とも呼ばれる。MOVNTPS や MOVNTQ) により利点が得られることがあります。通常、ストアは (キャッシュラインがまだない場合) キャッシュ階層にキャッシュラインを割り当てます。そのキャッシュラインが追い出されると、“ダーティー・ライトバック” が発生しメインメモリーへの書き込みが行われるため、バスのバンド幅をさらに消費することになります。

16 ビット形式 (rgb565) の入力ピクセルを 32 ビット (rgb8888) に変換する色空間変換ルーチンで、960×540 のイメージを処理する場合、全体のワーク量は 3MB になります。これは、インテル® Atom™ プロセッサーの L2 キャッシュのサイズ (512KB) を大きく超えています。このルーチンで、_mm_stream 組込み関数を使用してストリーミング・ストアを実装したところ、パフォーマンスが 1.4 倍も向上しました! 図 6 は、このコードの一部です。

for(j=0; j<w0_8; j+=8)
{
  ………………………………………..
  _mm_stream_si128((__m128i*)&rgba[j], rgba8888_0);
  _mm_stream_si128((__m128i*)&rgba[j+4], rgba8888_1);
}

図 6. ストリーミング・ストアの例

表 1 は、コード変更によってもたらされたパフォーマンスの向上をまとめたものです。バッファーを 16 バイト境界でアライメントしたケースでは、カーネルでメモリー・トラフィックがボトルネックになりました。ストリーミング・ストアを使用したケースでは、すべての “ダーティー・ライトバック” が排除され、バスが解放されたため、入力バッファーへのロードを迅速に行えるようになりました。また、インテル® コンパイラーを使用したケースでは、コンパイラーによって CSC アルゴリズムがベクトル化されたため、組込み関数を用いた場合とほぼ同じパフォーマンスが得られました。

色空間変換 時間 (ミリ秒)
ベースラインの Visual Studio* 2010 コンパイラー 25,430
インテル® コンパイラー /QxSSSE3_ATOM オプション 9,350
インテル® SSE2 組込み関数 9,520
16 バイトでアライメント 9,000
一時的でない (ストリーミング) ストア 6,210

表 1: 色空間変換の最適化によるスピードアップ

まとめ

この記事では、インテル® SSE 組込み関数によりすでに最適化されているコードから、さらにパフォーマンスを引き出すさまざまなヒントを紹介しました。以下のグラフは、MP3 デコーダーの例でストアフォワードを排除した場合、アライメントされていない問題を緩和した場合、除算を逆数による乗算に置換した場合、インテル® SSE 組込み関数を使用するように iMDCT ルーチンを変更した場合の各パフォーマンスの向上を示しています。


図 7. MP3 デコーダーにおけるインクリメンタルなパフォーマンスの向上

追加の資料として、「参考文献」に掲載されている資料と『インテル® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアル』をお読みになることをお勧めします。また、インテル® VTune™ Amplifier を利用してワークロードの hotspot を発見したり、簡単にパフォーマンスを向上できるインテル® コンパイラーのオプションも試してください。

パフォーマンスに関する注意事項

性能に関するテストに使用されるソフトウェアとワークロードは、性能がインテル® マイクロプロセッサー用に最適化されていることがあります。SYSmark* や MobileMark* などの性能テストは、特定のコンピューター・システム、コンポーネント、ソフトウェア、操作、機能に基づいて行ったものです。結果はこれらの要因によって異なります。製品の購入を検討される場合は、他の製品と組み合わせた場合の本製品の性能など、ほかの情報や性能テストも参考にして、パフォーマンスを総合的に評価することをお勧めします。

MP3 デコーダーの例は、インテル® コンパイラー 13.0 により第 2 世代インテル® Atom™ マイクロアーキテクチャー向けにコンパイルされ、インテル® Atom™ プロセッサー Z2760 (開発コード名: Cloverview) を搭載した Android* フォン (開発用サンプル) で実行されました。MP3 デコードのパフォーマンス・テストでは、4 分間の曲を入力として使い、オフラインでのデコード時間が評価されました (つまり、リアルタイム・プレイバック速度に関係なく、どれだけ速くデコードできるかが評価されました)。サブバンド合成カーネルと色空間変換カーネルも同様の方法でコンパイルされ、Cloverview✝ を搭載した 32 ビットの Windows 7 ベースの Samsung* 500T タブレットで実行されました。パフォーマンス・テストでは、カーネルを一定回数繰り返し実行して、その処理時間が測定されました。

参考文献

インテルの組込み関数のガイド (英語)
http://software.intel.com/en-us/articles/intel-intrinsics-guide

『Intel® 64 and IA-32 Architectures Software Developer’s Manual』 (英語) の 第 2 巻
http://download.intel.com/products/processor/manual/325383.pdf

『インテル® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアル』
http://www.intel.com/content/dam/www/public/ijkk/jp/ja/documents/developer/248966-024JA.pdf

ストアフォワードのブロック (英語)
http://software.intel.com/en-us/forums/topic/333586

アライメントされていないメモリーアクセスの影響の軽減 (英語)
http://software.intel.com/en-us/articles/reducing-the-impact-of-misaligned-memory-accesses

インテル® ストリーミング SIMD 拡張命令 (インテル® SSE) (英語)
http://www.songho.ca/misc/sse/sse.html

著作権と商標について

Intel、インテル、Intel ロゴ、Intel Atom、Intel Core、Pentium、Xeon、MMX、VTune は、アメリカ合衆国および / またはその他の国における Intel Corporation の商標です。
© 2013 Intel Corporation. 無断での引用、転載を禁じます。
* その他の社名、製品名などは、一般に各社の表示、商標または登録商標です。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事