2

在数字滤波 C++ 应用程序中,我使用std::inner_product(with std::vector<double>and 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]);
}

通常,我使用二阶带通 IIR 滤波器,这意味着filterNumfilterDenom(滤波器的分子和分母系数)的大小为 5。data是包含输入样本的向量。

4

2 回答 2

1

如果您只是直接编写代码,那么从中获得 2 的额外因子应该不难。部分原因可能是消除了 inner_product 的一些一般性,但也有一些原因是消除了双端队列的使用——如果你只保留一个指向输入数组的指针,你可以索引它并从过滤器数组中删除内循环,并在外循环中增加指向输入数组的指针。

每个 inner_products 都必须通过双端队列使用迭代器,

然后大部分(编码)工作变成了处理边缘条件。

并把那个除法去掉——它应该是乘以一个在循环外计算的常数。

内积本身是非常高效的(没有什么可做的),但它需要在每次通过内循环时增加两个迭代器。没有显式的循环展开,但一个好的编译器可以如此简单地展开循环。并且编译器更有可能知道在遇到指令缓存问题之前将循环展开多远。

Deque 迭代器在纯指针上的效率不如 ++。每个 ++ 至少有一个测试,并且可能有多个作业。

这是一个简单(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 回答
0

您可以要求 GCC 在并行模式下计算大多数算法,<algorithms>如果<numeric>您的数据集非常高,它可能会提高性能(我认为它实际上只在内部使用 OpenMP)。

但是,在小型数据集上,它可能会影响性能。

与其他解决方案进行比较将非常受欢迎!

http://gcc.gnu.org/onlinedocs/libstdc++/manual/parallel_mode.html

于 2013-08-05T02:39:59.857 回答