【问题标题】:Digital filter and std::inner_product optimization数字滤波器和 std::inner_product 优化
【发布时间】:2012-04-18 02:16:33
【问题描述】:

在数字滤波 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]);
}

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

【问题讨论】:

  • 您是否分析了调试版本?您希望在申请中花费更多时间吗?反正速度够快吗?
  • 我确实使用 callgrind 对应用程序进行了概要分析,并使用 GCC 选项 -O1 -g。由于应用程序应用了数千个过滤器,我得到的结果并不奇怪。我已经使用 OpenMP 并行应用了多个过滤器(它工作得很好,我的 Xeon 2 x 6 核机器上的速度为 x24)。但是,任务仍然很艰巨,如果我能获得 2 倍的速度,超过 1 小时的执行时间,它远非微不足道!而且它不是只运行一次的应用程序类型...
  • 直接用双数组写就行了;这将需要 10 分钟,您可以比较性能。我怀疑你会对 deque 和 inner_product 真正的“快速”程度感到震惊(但请确保你不旋转数组,尝试模拟 deque 在内部所做的事情)。
  • 好的,现在我们都想看看纯版本的速度有多快。完成后在结果中添加评论。

标签: c++ filtering numeric


【解决方案1】:

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

每个内部产品都必须通过双端队列使用迭代器,

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

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

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

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;
    }

请注意,遗漏了一些杂乱的细节,这与您的过滤器不同,但它给出了想法。外部循环内部的内容很容易适合现代指令缓存。编译器可以展开内部循环。大多数现代架构可以并行进行加法和乘法。

【讨论】:

  • +1。我敢打赌它提高了 2 倍以上。示例中所做的大部分工作是让数据进出双端队列并遍历它们。只需在 data 数组上就地进行处理,并让 out 成为预分配的向量,您可以将输出写入正确的位置。信号处理是您想要放弃所有花哨的数据结构而只使用原始内存缓冲区的地方。
【解决方案2】:

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

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

欢迎与其他解决方案进行比较!

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

【讨论】:

    猜你喜欢
    • 2012-04-06
    • 2017-03-17
    • 1970-01-01
    • 2015-01-08
    • 2017-01-17
    • 2014-12-11
    • 1970-01-01
    • 1970-01-01
    • 2013-02-20
    相关资源
    最近更新 更多