【问题标题】:Fast C++ sine and cosine alternatives for real-time signal processing用于实时信号处理的快速 C++ 正弦和余弦替代方案
【发布时间】:2019-07-23 10:43:34
【问题描述】:

我需要实现一个实时同步正交检测器。检测器接收输入数据流(来自 PCI ADC)并返回谐波幅度w。有简化的 C++ 代码:

double LowFreqFilter::process(double in)
{
   avg = avg * a + in * (1 - a);
   return avg;
}


class QuadroDetect
{
   double wt;
   const double wdt;

   LowFreqFilter lf1;
   LowFreqFilter lf2;

   QuadroDetect(const double w, const double dt) : wt(0), wdt(w * dt)
   {}

   inline double process(const double in)
   {
      double f1 = lf1.process(in * sin(wt));
      double f2 = lf2.process(in * cos(wt));
      double out = sqrt(f1 * f1 + f2 * f2);
      wt += wdt;
      return out;
   }
};

我的问题是 sincos 计算需要太多时间。有人建议我使用预先计算的sincos 表,但可用的ADC 采样频率不是w 的倍数,因此存在片段拼接问题。 sincos 计算是否有任何快速的替代方案?如有任何关于如何提高此代码性能的建议,我将不胜感激。

UPD 不幸的是,我写错了代码,去掉了过滤调用,代码就失去了意义。谢谢 Eric Postpischil。

【问题讨论】:

  • 什么平台?您是否尝试过 MKL 的矢量化函数?
  • 如果表格的采样率足够小,您可以对表格中的值之间的值进行简单的线性插值。
  • 根据您有多少信噪比容差,您可以简单地创建一个相当大的表并在值之间执行线性插值。不需要“片段拼接”,不管你是什么意思。我从第一手经验中知道,这种技术非常适用于一个非常知名的 DJ 应用程序中的实时音频重采样;)
  • 它应该适用于 Linux 和 Windows,目前我们适用于 Windows。你能澄清一下MKL吗?
  • 还要注意这个类只处理一个样本。这本质上对矢量化技术没有帮助。理想情况下,您应该拆分这些操作,以便可以并行完成多个计算。

标签: c++ optimization signal-processing trigonometry


【解决方案1】:

我知道一个适合您的解决方案。回忆一下角度和的正弦和余弦的学校公式:

sin(a + b) = sin(a) * cos(b) + cos(a) * sin(b)
cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b)

假设wdtwt角度的一个小增量,那么我们得到下一次sincos的递归计算公式:

sin(wt + wdt) = sin(wt) * cos(wdt) + cos(wt) * sin(wdt)
cos(wt + wdt) = cos(wt) * cos(wdt) - sin(wt) * sin(wdt)

我们只需要计算一次sin(wdt)cos(wdt) 值。对于其他计算,我们只需要加法和乘法运算。递归可以从任何时刻开始,因此我们可以用精确计算的值逐次替换,以避免无限期的错误累积。

有最终代码:

class QuadroDetect
{
   const double sinwdt;
   const double coswdt;
   const double wdt;

   double sinwt = 0;
   double coswt = 1;
   double wt = 0;

   QuadroDetect(double w, double dt) :
      sinwdt(sin(w * dt)),
      coswdt(cos(w * dt)),
      wdt(w * dt)
   {}

   inline double process(const double in)
   {
      double f1 = in * sinwt;
      double f2 = in * coswt;
      double out = sqrt(f1 * f1 + f2 * f2);

      double tmp = sinwt;
      sinwt = sinwt * coswdt + coswt * sinwdt;
      coswt = coswt * coswdt - tmp * sinwdt;

      // Recalculate sinwt and coswt to avoid indefinitely error accumulation
      if (wt > 2 * M_PI)
      {
         wt -= 2 * M_PI;
         sinwt = sin(wt);
         coswt = cos(wt);
      }

      wt += wdt;
      return out;
   }
};

请注意,这种递归计算提供的结果不如 sin(wt) cos(wt) 准确,但我使用它并且效果很好。

【讨论】:

  • 根据任务的不同,您可能需要不时重新计算 sin 和 cos,以免错误无限累积。
  • @AlexeyFrunze。是的,递归可以从任何时候开始,我们可以用精确计算的时间来替换这些值。感谢您的评论。
  • 重新计算的方式不正确。 'wt' 只能在正好是 '2*M_PI' 时设置为 0。所以 'wt -= 2*M_PI 更合适。并且应该在重新计算 'sinwt' 和 'coswt' 之前设置。
【解决方案2】:

如果您可以使用 std::complex,则实现会变得简单得多。技术上它与@Dmytro Dadyka 的解决方案相同,因为复数以这种方式工作。如果优化器运行良好,它应该同时运行。

class QuadroDetect
{
public:
    std::complex<double> wt;
    std::complex <double> wdt;

    LowFreqFilter lf1;
    LowFreqFilter lf2;

    QuadroDetect(const double w, const double dt)
    :   wt(1.0, 0.0)
    ,   wdt(std::polar(1.0, w * dt))
    {
    }

    inline double process(const double in)
    {
        auto f = in * wt;
        f.imag(lf1.process(f.imag()));
        f.real(lf2.process(f.real()));
        wt *= wdt;
        return std::abs(f);
    }
};

【讨论】:

  • 我也喜欢使用复数进行此类计算。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-23
  • 2016-06-16
相关资源
最近更新 更多