代码或编译器:为iPhone4及更高版本优化C语言中的IIR过滤器

代码或编译器:为iPhone4及更高版本优化C语言中的IIR过滤器,iphone,c,optimization,floating-point,llvm,Iphone,C,Optimization,Floating Point,Llvm,我一直在分析我即将完成的项目,我发现大约四分之三的CPU时间都花在这个IIR过滤函数上(目前在目标硬件上,它在大约一秒钟内被调用数十万次)因此,在其他一切都运行良好的情况下,我想知道它是否可以针对我的特定硬件和软件目标进行优化。我的目标只有iPhone4和更新版本,只有iOS4.3和更新版本,只有LLVM4.x。如果有收益的话,稍微有点不精确也许是可以的 static float filter(const float a, const float *b, const float c, float

我一直在分析我即将完成的项目,我发现大约四分之三的CPU时间都花在这个IIR过滤函数上(目前在目标硬件上,它在大约一秒钟内被调用数十万次)因此,在其他一切都运行良好的情况下,我想知道它是否可以针对我的特定硬件和软件目标进行优化。我的目标只有iPhone4和更新版本,只有iOS4.3和更新版本,只有LLVM4.x。如果有收益的话,稍微有点不精确也许是可以的

static float filter(const float a, const float *b, const float c, float *d, const int e, const float x)
{
    float return_value = 0;

    d[0] = x;
    d[1] = c * d[0] + a * d[1];

    int j;

    for (j = 2; j <= e; j++) {
        return_value += (d[j] += a * (d[j + 1] - d[j - 1])) * b[j];
    }

    for (j = e + 1; j > 1; j--) {
        d[j] = d[j - 1];
    }
    return (return_value);
}
静态浮点过滤器(常量浮点a、常量浮点*b、常量浮点c、浮点*d、常量整数e、常量浮点x)
{
浮点返回_值=0;
d[0]=x;
d[1]=c*d[0]+a*d[1];
int j;
对于(j=2;j1;j--){
d[j]=d[j-1];
}
返回值(返回值);
}
如果有可能在默认编译器优化之外进行优化,我们将非常感谢您提出的任何加速建议,并对您的意见感兴趣。我想知道NEON SIMD是否会有所帮助(这对我来说是一个新领域),或者VFP是否可以被利用,或者LLVM自动矢量化是否会有所帮助

我尝试了以下LLVM标志:

-ffast数学(没有显著差异)

-O4(在iPhone4S上有了很大的不同,时间缩短了25%,但在我的最低目标设备iPhone4上没有显著的差异,提高它是我的主要目标)

-O3-mllvm-unroll允许部分-mllvm-unroll运行时-funsafe数学优化-ffast数学-mllvm-vectorize-mllvm-bb矢量化仅对齐(Hal Finkel幻灯片中的LLVM自动矢量化标志:,使速度低于Xcode发布目标的默认LLVM优化)

打开其他标志、不同方法和对函数的更改。我更喜欢不使用输入和返回类型和值。这里实际上讨论了如何使用氖内禀函数来表示FIR:但我在这方面没有足够的经验来成功地将这些信息应用到我自己的案例中。谢谢你的澄清

编辑我很抱歉没有早点注意到这一点。在调查了aka.nice的建议之后,我注意到为e、a和c传递的值总是相同的,并且我在运行之前就知道它们,所以合并这些信息的方法是一种选择

我宁愿不去管输入和返回的类型和值

不过,将渲染从浮点移动到整数会有很大帮助

将该更改本地化到您提供的实现中是没有用的。但如果您将其扩展为仅将FIR作为整数重新实现,它可以很快获得回报(除非保证大小总是非常小,否则转换/移动时间的成本会更高)。当然,将渲染图的较大部分移动到整数将带来更大的增益,并且需要更少的转换


另一个需要考虑的问题是查看Accelerate.framework中的实用程序(有可能使您不必编写自己的asm)。

以下是可以对代码进行的一些转换,以使用vDSP例程。这些转换使用名为T0、T1和T2的各种临时缓冲区。每一个都是一个浮点数组,有足够的空间容纳e-1元素

首先,使用临时缓冲区来计算
a*b[j]
。这将更改原始代码:

for (j = 2; j <= e; j++) {
    return_value += (d[j] += a * (d[j + 1] - d[j - 1])) * b[j];
}
接下来,将vDSP_vmul升级为vDSP_vma(向量乘加),以计算
d[j]+d[j+1]*T0[j-2]

vDSP_vsmul(b+2, 1, &a, T0, 1, e-1);
vDSP_vmul(d+3, 1, T0, 1, T1, 1, e-1);
for (j = 2; j <= e; j++)
    return_value += (d[j] += T1[j-2] - d[j-1] * T0[j-2];
vDSP_vsmul(b+2, 1, &a, T0, 1, e-1);
vDSP_vma(d+3, 1, T0, 1, d+2, 1, T1, 1, e-1);
for (j = 2; j <= e; j++)
    return_value += (d[j] = T1[j-2] - d[j-1] * T0[j-2];
vDSP_vsmul(b+2,1和a,T0,1,e-1);
vDSP_-vma(d+3,1,T0,1,d+2,1,T1,1,e-1);

对于(j=2;j我尝试了这个小练习,用延迟操作符z重写你的过滤器

例如,对于e=4,我重命名了输入u和输出y

d1*z= u
d2*z= c*u + a*d1
d3*z= d2 + a*(d3-d1*z)
d4*z= d3 + a*(d4-d2*z)
d5*z= d4 + a*(d5-d3*z)
y = (b2*d3*z + b3*d4*z + b4*d5*z)
请注意,di是过滤器状态。
d3*z是d3的下一个值(在代码中似乎是变量d2)
然后,您可以消除di以在z中写入传递函数y/u。
然后,通过分解/简化上述传递函数,您将发现最小表示只需要e状态。
分母是
z*(z-a)^3
,即0处的一个极点,a处的另一个极点具有多重性(e-1)。

然后,可以将过滤器置于标准状态空间矩阵表示形式中:

z*X = A*X + B*u
y = C*X + d*u
使用特殊形式的极点,可以将转移分解为部分分数展开,并获得这种特殊形式的矩阵A和B(类似于matlab的符号)

不过,C&d有点不容易…
它们是从分子和部分分数展开的直接项中提取的
它们是bi、c(阶1)和a(阶e)中的多项式
对于e=4,我有

C=[   a^3*b2            - a^2*b3                      + a*b4 ,
     -a^2*b2              + a*b3                + (c-a^2)*b4 ,
        a*b2        + (c-a^2)*b3 + (-2*a^2*c-2*a^2-a+a^4)*b4 ,
  (c-a^2)*b2 + (-a^2-a^2*c-a)*b3 +         (-2*a*c+2*a^3)*b4 ]
d=     -a*b2            - a*c*b3                    + a^2*b4
如果您可以在e&d中找到循环,并预计算它们
然后可以将滤波器简化为以下简单的向量运算:

z*X = a*[ 0; x2 ; x3 ; x4 ... xe ] + [x2 ; x3 ; x4 ... xe ; u ];
Y = C*[ x1 ; x2 ; x3 ; x4 ... xe ] + d*u
或表示为函数
(Xnext,y)=过滤器(X,u,a,C,d,e)
伪代码:

y = dot_product( C , X) + d*u; // (like BLAS _DOT)
Xnext(1:e-1) = X(2:e); // this is a memcopy (like BLAS _COPY)
Xnext(e)=u;
X(1)=0;
Xnext=a*X+Xnext; // this is an inplace vector muladd (like BLAS _AXPY)

X=Xnext; // another memcopy outside the function (can be moved inside).
请注意,如果您使用BLAS函数,您的代码将可移植到许多硬件,而不仅仅是Applecentric,我想性能不会有太大的不同

编辑:关于部分分数展开

纯部分分数展开将给出对角状态空间表示,以及一个充满1的矩阵B。这也是一个有趣的变体。(并行过滤器)

我上面使用的变体更像是级联或阶梯(系列过滤器)。

您是否尝试使用最新的GCC(以及优化标志,至少是
-O3
)编译该函数?(这可能比GCC4.7或主干(即未来的4.8)要好得多).我用LLVM编译,我试过-O3和-O4。如前所述,-O4在另一台设备上产生了很大的不同,但不幸的是,在我所担心的特定硬件上没有。这不是IIR而不是FIR吗?似乎输出
d[j]
部分取决于早期的输出z*X = a*[ 0; x2 ; x3 ; x4 ... xe ] + [x2 ; x3 ; x4 ... xe ; u ]; Y = C*[ x1 ; x2 ; x3 ; x4 ... xe ] + d*u
y = dot_product( C , X) + d*u; // (like BLAS _DOT)
Xnext(1:e-1) = X(2:e); // this is a memcopy (like BLAS _COPY)
Xnext(e)=u;
X(1)=0;
Xnext=a*X+Xnext; // this is an inplace vector muladd (like BLAS _AXPY)

X=Xnext; // another memcopy outside the function (can be moved inside).