2

デジタル フィルタリング C++ アプリケーションでは、std::inner_product(および と一緒std::vector<double>std::deque<double>) を使用して、データ サンプルごとに、フィルター係数と入力データの間の内積を計算します。アプリケーションのプロファイリングを行った後、実行時間の 85% 以上がstd::inner_product!に費やされていることがわかりました。

std::inner_productたとえばGCCでは、どの程度まで最適化されていますか? SIMD命令を使用していますか?ループ展開を実行しますか? それを確認する方法は?これに基づいて、カスタム内積関数を実装する価値はありますか (特に係数の数が少ない場合)? (ただし、機能を可能な限り汎用的に保ちたい)

より具体的には、これはフィルターを適用するために使用するコードです。

std::deque<double> in(filterNum.size(), 0.0);
std::deque<double> out(filterDenom.size() - 1, 0.0);
const double gain = filterDenom.back();

for (unsigned int s = 0, size = data.size(); s < size; ++s) {
    in.pop_front();
    in.push_back(data[s] / gain);

    data[s] = inner_product(in.begin(), in.end(), filterNum.begin(),
        -inner_product(out.begin(), out.end(), filterDenom.begin(), 0.0));

    out.pop_front();
    out.push_back(data[s]);
}

通常、私は 2 次バンドパス IIR フィルターを使用します。これは、filterNumおよびfilterDenom(フィルターの分子と分母の係数) のサイズが 5であることを意味します。dataは、入力サンプルを含むベクトルです。

4

2 に答える 2

1

コードを直接記述するだけであれば、これをさらに 2 倍することは難しくありません。その一部は、inner_product の一般性の一部を削除することから生じる可能性がありますが、deques の使用を排除することなどから生じるものもあります。入力配列へのポインターを保持するだけで、ポインターをインデックスオフし、フィルター配列をオフにすることができます外側のループで入力配列へのポインタをインクリメントします。

これらの inner_products のそれぞれは、両端キューを介して反復子を使用する必要があります。

(コーディング) 作業のほとんどは、エッジ条件の処理になります。

そして、その除算をそこから取り出します-それは、ループの外側で計算された定数による乗算でなければなりません。

内積自体は非常に効率的ですが (そこで行うことはあまりありません)、内部ループを通過するたびに 2 つの反復子をインクリメントする必要があります。明示的なループのアンロールはありませんが、優れたコンパイラはそのような単純なループをアンロールできます。また、コンパイラーは、命令キャッシュの問題が発生する前に、ループをどこまで展開すればよいかを認識している可能性が高くなります。

Deque イテレータは、純粋なポインタに対する ++ ほど効率的ではありません。すべての ++ に少なくとも 1 つのテストがあり、複数の割り当てがある場合があります。

これは、単純な (FIR) フィルターがどのように見えるかです。(ループの外に出る) エッジ条件のコードを含めません。

double norm = 1.0/sum;
double *p = data.values(); // start of input data
double *q = output.values();  // start of output buffer
int width = data.size() - filter.size();
for( int i = 0; i < width; ++i )
    {
    double *f = filter.values();
    double accumulator = ( f[0] * p[0] );
    for( int j = 1; j < filter.size(); ++j )
        {
        accumulator += ( f[i] * p[i] );
        }
    *q++ = accumulator * norm;
    }

面倒な詳細が省略されていることに注意してください。これはフィルターと同じではありませんが、アイデアは得られます。外側のループの内側にあるものは、最新の命令キャッシュに簡単に収まります。内側のループは、コンパイラによって展開される場合があります。最新のアーキテクチャのほとんどは、加算と乗算を並行して実行できます。

于 2012-04-04T13:13:30.673 に答える