代码之家  ›  专栏  ›  技术社区  ›  markzzz

在计算中,是否可以对串行依赖项使用simd,比如指数移动平均滤波器?

  •  1
  • markzzz  · 技术社区  · 7 年前

    我正在处理多个(独立的) Exponential Moving Average 1-极 filters 在我的音频应用程序中有不同的参数,目的是以音频速率平滑每个参数值:

    for (int i = 0; i < mParams.GetSize(); i++) {
        mParams.Get(i)->SmoothBlock(blockSize);
    }
    
    ...
    
    inline void SmoothBlock(int blockSize) {
        double inputA0 = mValue * a0;
    
        for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex++) {
            mSmoothedValues[sampleIndex] = z1 = inputA0 + z1 * b1;
        }
    }   
    

    我想利用CPU SIMD 指令,并行处理它们,但我不确定如何实现这一点。

    事实上, z1 递归:不能“打包”双重数组考虑“以前的值”,对吗?

    也许有没有一种方法可以正确地组织不同过滤器的数据并并行处理它们?

    欢迎您提供任何提示或建议!

    请注意:我没有几个信号路径。任何参数代表(唯一)处理信号的不同控制。假设我有一个sin信号:参数1将影响增益,参数2间距,参数3滤波器截止,参数4平移,等等。

    2 回复  |  直到 7 年前
        1
  •  3
  •   Peter Cordes    7 年前

    如果有一个闭式公式 n 向前一步,您可以使用它来回避串行依赖性。如果可以用与1步相同的操作用不同的系数计算,那么广播就是您所需要的。

    就像在这种情况下, z1 = c + z1 * b 所以我们两次申请

    # I'm using Z0..n as the elements in the series your loop calculates
    Z2 = c + (c+Z0*b)*b
       = c + c*b + Z0*b^2
    

    c + c*b 和 b^2 这两个都是常量,如果我正确理解了您的代码,所有C变量实际上只是C变量,而不是数组引用的伪代码。(所以除了你的 z1 循环不变)。


    所以如果我们有一个2元素的simd向量,从z0和z1开始,我们可以 每个 他们中的2个前进,得到z2和z3。

    void SmoothBlock(int blockSize, double b, double c, double z_init) {
    
        // z1 = inputA0 + z1 * b1;
        __m128d zv = _mm_setr_pd(z_init, z_init*b + c);
    
        __m128d step2_mul = _mm_set1_pd(b*b);
        __m128d step2_add = _mm_set1_pd(c + c*b);
    
        for (int i = 0; i < blockSize-1; i+=2) {
            _mm_storeu_pd(mSmoothedValues + i, zv);
            zv = _mm_mul_pd(zv, step2_mul);
            zv = _mm_add_pd(zv, step2_add);
           // compile with FMA + fast-math for the compiler to fold the mul/add into one FMA
        }
        // handle final odd element if necessary
        if(blockSize%2 != 0)
            _mm_store_sd(mSmoothedValues+blockSize-1, zv);
    }
    

    用 float +AVX(每向量8个元素),你应该

    __m256 zv = _mm256_setr_ps(z_init, c + z_init*b,
                             c + c*b + z_init*b*b,
                             c + c*b + c*b*b + z_init*b*b*b, ...);
    
    // Z2 = c + c*b + Z0*b^2
    // Z3 = c + c*b + (c + Z0*b) * b^2
    // Z3 = c + c*b + c*b^2 + Z0*b^3
    

    加/多因子因子将用于8个步骤。

    通常人们使用 浮动 对于SIMD,因为每个向量的元素数量是原来的两倍,内存带宽/缓存占用空间的一半,所以通常会得到2倍的加速系数 浮动 . (每个时钟相同的矢量/字节数。)


    例如,Haswell或SandyBridge上的上述循环将以每8个周期一个向量的速度运行,在 mulpd (5个周期)+ addps (3个周期)。 我们产生2 double 每个向量的结果,但与每时钟吞吐量增加1个MUL和1个MUL相比,这仍然是一个巨大的瓶颈。我们错过了8倍的吞吐量。

    (或者,如果使用一个fma而不是mul->add编译,则我们有5个周期延迟)。

    回避串行依赖性不仅对SIMD有用,而且对于避免FP ADD/MUL(或FMA)延迟上的瓶颈也有用,这将进一步加快速度,达到FP ADD+MUL延迟与ADD+MUL吞吐量之比。

    只需展开更多,并使用多个向量,例如 zv0 ,请 zv1 , zv2 , zv3 . 这也会增加您一次完成的步骤的数量。例如,16字节向量 浮动 有4个向量,4x4=16步。

        2
  •  3
  •   Dmytro Dadyka    7 年前

    在输入信号是 Heaviside step function .您希望获得对该函数的筛选器响应,该函数称为 Step response . 在这种情况下,递归可以消除。首先,让我们展开递归几个步骤。

    z[1] = in + z[0]*b
    z[2] = in + z[1]*b = in + (in + z[0]*b)*b  = in*(1 + b) + z[0]*b^2
    z[3] = in + z[2]*b = in*(1 + b + b^2) + z[0]*b^3
    z[4] = in + z[3]*b = in*(1 + b + b^2 + b^3) + z[0]*b^4
    

    从上一个等式:

    z[1] = in*(1 + b + b^2 + b^3) + z[-3]*b^4
    z[2] = in*(1 + b + b^2 + b^3) + z[-2]*b^4
    z[3] = in*(1 + b + b^2 + b^3) + z[-1]*b^4
    z[4] = in*(1 + b + b^2 + b^3) + z[0]*b^4
    

    现在很容易以矢量化的形式重写它。

    in' = {in, in, in, in};
    z' = in' * (1 + b + b^2 + b^3) + z'*b^4
    

    其中“'”表示矢量寄存器或单SIMD寄存器。现在可以很容易地将其转换为immintrin指令。请注意,现在您不能更改任何样本的输入值,而是有时更改四个样本的倍数。

    此外,可以将两个或多个SIMD寄存器表示为一个向量,并进一步展开递归。这将提高性能,因为更好的管道利用率,但不要过度使用它,否则您将没有足够的寄存器。

    推荐文章