インテル® AVX 命令を使用した complex float データ型の IIR フィルターの実装

インテル® IPP特集

この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Intel® AVX Realization Of IIR Filter For Complex Float Data」(http://software.intel.com/en-us/articles/intel-avx-realization-of-iir-filter-for-complex-float-data/) の日本語参考訳です。


はじめに

この記事では、インテル® AVX の SIMD (Single Instruction Multiple Data) 拡張命令セットを使用した複素数 IIR (Infinite Impulse Response、無限インパルス応答) フィルターの実装について説明します。実装にあたっては、IIR フィルターの性質であるフィードバック依存性 (次の出力ポイントが前の出力ポイントに依存する) に対処する必要があります。この記事では、一般的な IIR のベクトル化手法とインテル® AVX 命令セット・アーキテクチャー (ISA) を使用した実装について説明します。実装の結果については、最後に考察します。



インテル® AVX 命令を使用した complex float データ型の IIR フィルターの実装

まず、一般的な IIR フィルターの方程式を見てみましょう。


方程式 1


図 1. 任意次数の IIR フィルターの構造
ここで、”a” と “b” は IIR 係数、”x” は入力データベクトル、”y” は出力データベクトル、”order” はフィルターの次数です。出力値は、前の出力値のセットに強く依存しています。ここでの実装の目標は、このフィードバック (各出力ポイントにおける、前のポイントへの依存性) を排除することです。float データ型とインテル® AVX 命令の幅を考慮して、8 つの要素が (complex データ型を処理する場合は 4 つ) の計算ループをベクトル化します。

この手法はほかのデータ型 (例えば、特定のベクトル要素の real データ型や double データ型) にも適用することができます。以降の説明では complex float データ型を対象にしています。つまり、以下の加算、減算、乗算はすべて複素数演算です。ここでの目標は、”a” 係数を再計算して 4 つの “y” 計算をベクトル化 (してフィードバックの遅延を排除) することです。方程式 1 は次のように表現できます。


方程式 2
ここで、Fx(n) は “x” のみに依存する関数で、単純な有限インパルス応答 (FIR) フィルターです。最初に “x” (FIR)、次に “y” による、2 つの連続したフィルタリングを行います。任意の 4 ポイントの FIR 部分は、類似の式 (簡単に説明するため、”order”=k+1 と仮定しています) で表現することができます。


方程式 3
IIR の FIR 部分は、インテル® AVX を使用し、ベクトル長に応じて 8 つ (4 つの複素数) のアンロールを行います。32 ビット境界でアラインされた “b.re” と “b.im” の 8 つのコピーを IIR 構造に格納する手法は、インテル® AVX アルゴリズム (初期化関数を参照) では一般的です。


図 2. FIR 部分の係数の設定
b0.re, b0.re, b0.re, b0.re, b0.re, b0.re, b0.re, b0.re
-b0.im, b0.im,-b0.im, b0.im,-b0.im, b0.im,-b0.im, b0.im
b1.re, b1.re, b1.re, b1.re, b1.re, b1.re, b1.re, b1.re
-b1.im, b1.im,-b1.im, b1.im,-b1.im, b1.im,-b1.im, b1.im
…………………..
bk.re, bk.re, bk.re, bk.re, bk.re, bk.re, bk.re, bk.re
-bk.im, bk.im,-bk.im, bk.im,-bk.im, bk.im,-bk.im, bk.im
以下のコードは、新しい 256 ビット・レジスターを使用して実装した IIR アルゴリズムの FIR 部分です (完全なアセンブラー・コードは、B 係数のアセンブラー関数を参照してください)。


図 3. アルゴリズムの FIR 部分
Load YMM reg = {x3.im, x3.re, x2.im, x2.re, x1.im, x1.re, x0.im, x0.re}
vmulps {bk.re, bk.re, bk.re, bk.re, bk.re, bk.re, bk.re, bk.re}+
Vshufps YMM reg = {x3.re, x3.im, x2.re, x2.im, x1.re, x1.im, x0.re, x0.im}
vmulps {-bk.im, bk.im,-bk.im, bk.im,-bk.im, bk.im,-bk.im, bk.im}
+…………………………………………………………………………………………………………………
…………………………………………………………………………………………………… +
Load YMM reg = {x3+k.im, x3+k.re, x2+k.im, x2+k.re, x1+k.im, x1+k.re, xk.im, xk.re}
vmulps {b0.re, b0.re, b0.re, b0.re, b0.re, b0.re, b0.re, b0.re} +
Vshufps YMM reg = {x3+k.re, x3+k.im, x2+k.re, x2+k.im, x1+k.re, x1+k.im, xk.re, xk.im}
vmulps {-b0.im, b0.im,-b0.im, b0.im,-b0.im, b0.im,-b0.im, b0.im} = Fx(n+0).re,im, Fx(n+1).re,im, Fx(n+2).re,im, Fx(n+3).re,im
//デスティネーション・ベクトルに一時的に格納します。
最初のステップの後、Fx(n) はすべてデスティネーション・ベクトルに保存されます。


方程式 4
フィードバックの遅延を排除するため、上記の式の 2 行目以降の yn を 1 行目の yn で、3 行目以降の yn+1 を 2 行目の yn+1 で、4 行目の yn+2 を 3 行目の yn+2 で置換します。例えば、yn+2 を以下のように置換します。


方程式 5
最初の 4 つの行の左辺は新しい出力ポイント yn、yn+1、yn+2、yn+3 、右辺は “古い” ポイント (yn-1 …yn-k) および新しい係数をともなう Fx(n+0)…Fx(n+3) の関数で、pState 構造の初期化中に計算されます (初期化関数を参照)。


方程式 6


方程式 7


方程式 8


方程式 9
complex float データ型と AVX 演算のベクトル化因数は 4 になります。バンドルの 4 つのポイントにおける新しい 4 セットの複素 “a” 係数を以下に示します。
  • a0n – バンドルの最初のポイント – 方程式 6
  • a1n – バンドルの 2 つ目のポイント – 方程式 7
  • a2n – バンドルの 3 つ目のポイント – 方程式 8
  • a3n – バンドルの 4 つ目のポイント – 方程式 9
また、Fx(n+0).re,im、Fx(n+1).re,im、Fx(n+2).re,im、Fx(n+3).re,im について追加の計算を行う必要があります。バンドルの各ポイントの係数を図 4 に示します。


図 4. IIR 部分の追加の三角の係数
1.
0,
0,
0,
1;
2.
a01,
0,
0,
1;
3.
a11,
a01,
0,
1;
4.
a21,
a11,
a01,
1;
最初の (FIR) ステップと同じように、新しい係数はすべて init ステージ (初期化関数を参照) で計算され、適切なアライメントのインテル® AVX 次数で格納されます (すべて “1” の部分は必要ないため省略)。


図 5. IIR 部分の係数の設定
a01.re,im, a11.re,im, a21.re,im, a31.re,im
a02.re,im, a12.re,im, a22.re,im, a32.re,im
...................
a0k.re,im, a1k.re,im, a2k.re,im, a3k.re,im
0.re,im, a01.re,im, a11.re,im, a21.re,im
0.re,im, 0.re,im, a01.re,im, a11.re,im
0.re,im, 0.re,im, 0.re,im, a01.re,im
フィードバックの依存性を排除するアルゴリズムのコストとして、4 つの各出力ポイントで 10 回の複素加算と 6 回の複素乗算が追加で行われます。


図 6. アルゴリズムの IIR 部分
vbroadcast to YMM = {yn-1.re, yn-1.re, yn-1.re, yn-1.re,yn-1.re, yn-1.re, yn-1.re, yn-1.re}
vmulps {a01.re, a01.im, a11.re, a11.im, a21.re, a21.im, a31.re, a31.im} +
vbroadcast to YMM = {yn-1.im, yn-1.im, yn-1.im, yn-1.im,yn-1.im, yn-1.im, yn-1.im, yn-1.im}
vmulps {a01.im, a01.re, a11.im, a11.re, a21.im, a21.re, a31.im, a31.re} +
……………………………… +
vbroadcast to YMM = {yn-k.re, yn-k.re, yn-k.re, yn-k.re,yn-k.re, yn-k.re, yn-k.re, yn-k.re}
vmulps {a0k.re, a0k.im, a1k.re, a1k.im, a2k.re, a2k.im, a3k.re, a3k.im} +
vbroadcast to YMM = {yn-k.im, yn-k.im, yn-k.im, yn-k.im,yn-k.im, yn-k.im, yn-k.im, yn-k.im}
vmulps {a0k.im, a0k.re, a1k.im, a1k.re, a2k.im, a2k.re, a3k.im, a3k.re} +
vbroadcast to YMM = { Fx(n+0), Fx(n+0), Fx(n+0), Fx(n+0)}re.im
vmulps { a0k.re,im, a1k.re,im, a2k.re,im, a3k.re,im } + (extra)
vbroadcast to YMM = { Fx(n+1), Fx(n+1), Fx(n+1), Fx(n+1)}re.im
vmulps { 0.re,im, a01.re,im, a11.re,im, a21.re,im } + (extra)
vbroadcast to YMM = { Fx(n+2), Fx(n+2), Fx(n+2), Fx(n+2)}re.im
vmulps { 0.re,im, 0.re,im, a01.re,im, a11.re,im } + (extra)

vbroadcast to YMM = { Fx(n+3), Fx(n+3), Fx(n+3), Fx(n+3)}re.im
vmulps { 0.re,im, 0.re,im, 0.re,im, a01.re,im } + (extra)
vload to YMM = { Fx(n+3), Fx(n+2), Fx(n+1), Fx(n+0)}re.im = vstore YMM = {Yn+0, Yn+1, Yn+2, Yn+3}re.im
デスティネーション・ベクトルに一時的に格納
このアプローチにより、バンドル全体のフィードバックが排除され、IIR フィルターのパフォーマンスはすべての SIMD アーキテクチャーの 2 倍以上になります。
上記で説明したアルゴリズムの完全な IIR ブロックスキームを以下に示します。このアルゴリズムは、インテル® インテグレーテッド・パフォーマンス・プリミティブ (インテル® IPP) ライブラリーを使用しています。


図 7. 完全な IIR アルゴリズムのブロックスキーム
IppStatus ippsIIRInitAlloc_32fc( IppsIIRState_32fc** ppState, const Ipp32fc* pTaps, int order, const Ipp32fc* pDlyLine);
タップの次数 = B0,B1,...,Border,A0,A1,...,Aorder
//この関数はメモリーを割り当てて任意の IIR フィルターの状態を初期化する

タップのノルム化: a1=A1/A0 , a2=A2/A0 , ..., border=Border/A0
タップは (6) – (9) に従って再計算され pState 構造の適切なアライメント (32 バイト境界) で、図 1 と図 4 の次数に従って 8 つの係数で格納される
IppStatus ippsIIR_32fc( const Ipp32fc *pSrc, Ipp32fc *pDst, int len,
IppsIIRState_32fc *pState )
{
//第 1 ステージ: FIR フィルターと bk 係数 (インテル® AVX で記述したコード) で "by x" をフィルタリング。一時出力は pDst ベクトル (図 2 および B 係数のアセンブラー関数)
//第 2 ステージ: 4 つのバンドル (インテル® AVX で記述したコード) と再計算した ak 係数で "by y" をフィルタリング (図 5 および A 係数のアセンブラー関数)
}



まとめ

IA32 アーキテクチャー向け IPP ライブラリーの IIR フィルター関数を利用した以前の実装では、フィードバック依存性のためポイントごとに処理され、ベクトル化されていませんでした。つまり、出力ポイントあたりのベクトル関数のパフォーマンスは 1 つのポイントの関数のパフォーマンスとほぼ同じでした。ベクトル化は、ベクトル長ではなく、フィルターの次数で行われていました。”C” コードと比較した場合のビジュアル・パフォーマンスは、フィルターの次数が 32 以上のときにのみ向上していました。

これほど大きな次数の IIR フィルターが信号処理で実際に使用されることはありません。32 未満の次数では、インテル® SSE を使用して IIR フィルターの実装を行っても、共通スキームで蓄積される命令のレイテンシーとフィードバックの依存性により、ベクトル化されていないバージョンではパフォーマンスが向上しませんでした。

ここで説明した (フィードバックの依存性を排除した) インテル® AVX 命令を使用した実装では、次数 6 以上のフィルターで SSE バージョンの 1.6 倍のスピードアップを達成しました (この値はソフトウェア・シミュレーターで計測されたものであり、実際の値は異なる可能性があることに注意してください)。インテル® AVX で最適化されたフィルターの実装は、IPP ライブラリーの将来のバージョンに追加される予定です。




付録 A: 関数コード

初期化関数
  • 初期化関数コードのダウンロード: http://software.intel.com/file/1066
フィルタリング関数
  • フィルタリング関数コードのダウンロード: http://software.intel.com/file/1057
B 係数のアセンブラー関数
  • B 係数のアセンブラー関数のダウンロード: http://software.intel.com/file/1038
A 係数のアセンブラー関数
  • A 係数のアセンブラー関数のダウンロード: http://software.intel.com/file/1036

参考文献

Intel® Integrated Performance Primitives for Intel® Architecture, vol.1 – Signal Processing, Filtering Functions, 6-103…6-130.



著者紹介

Igor Astakhov は、インテル社のソフトウェア & ソリューション・グループのデベロップメント・ソフトウェア・エンジニアリング・マネージャー (ビジュアル・コンピューティング・ソフトウェア部門、CIP、インテル® IPP) で、すべての既存および将来のインテル® アーキテクチャー向けに、インテル® IPP ライブラリーの開発を行っています。

インテル® ソフトウェア製品のパフォーマンス/最適化に関する詳細は、最適化に関する注意事項 (英語) を参照してください。

編集部追加

最新のインテル® IPP 7.0 では、以下の記事(英語)にリストされている多数の関数についてインテル® AVX による最適化が適用済みです。
http://software.intel.com/en-us/articles/intel-ipp-functions-optimized-for-intel-avx-intel-advanced-vector-extensions/

第2世代インテル® Core™ プロセッサー・ファミリーの性能を活かしきれていないとお考えの方は、是非ともインテル® IPP 並びにインテル® C++ Composer XE をお試しください。評価版のダウンロード、お問い合わせはエクセルソフト株式会社まで。

タイトルとURLをコピーしました