算法优化:通过计算或查找表的对数

时间:2012-10-25 22:39:23

标签: algorithm optimization lookup-tables logarithm

我正在尝试优化音频算法,必须在每个步骤中计算两个算法,如下所示。现在我已经读过,没有在多项式时间内运行的对数算法。 我的问题是,如果通过查找表执行所有对数是有意义的,因为它们总是相同的,尽管存在大量内存访问的缺点?

for(int f=1;f<11000;f++){
    for(int fk=1;fk<1000;fk++){
        int k = ceil(12 * log2f((f - 0.5) / fk));
    }
} 

我正在用C ++编程。

非常感谢!

4 个答案:

答案 0 :(得分:5)

如果您真正需要的是

ceil(12 * log2(/* something */))

然后有一个非常简单的O(1)计算,使用只有12个值的表格。

  1. 使用frexp()将值拆分为指数和尾数。 (这只是位操作,因此只需要几个周期。)

  2. 在预先计算的第12个根的权力列表(除以2)中查看尾数,最多可以进行4次比较。

  3. 结果是12 *(指数-1)+最小根的索引&gt; =尾数。

  4. 编辑添加:

    实际上有一个更好的解决方案,因为二十二根的权力可以合理地均匀分布。如果将[0.5,1.0)(由frexp返回的尾数范围)除以17个均匀间隔的子范围,则每个子范围将落入两个可能的返回值之一。因此,如果将每个子范围与索引关联到根向量中,则只需将目标(在该范围内)与单个根进行比较。

    对于我来说,写一致的英语为时已晚,所以你必须满足于代码:

    int Ceil12TimesLog2(double x) {
      int exp;
      double mantissa = std::frexp(x, &exp) * 2;
      int idx = indexes[int((mantissa - 1.0) * 17)];
      return 12 * (exp - 1) + (mantissa <= roots[idx] ? idx : idx + 1);
    }
    
    // Here are the reference tables.
    
    double roots[12] = {
      0x1.0000000000000p+0,
      0x1.0f38f92d97963p+0,
      0x1.1f59ac3c7d6c0p+0,
      0x1.306fe0a31b715p+0,
      0x1.428a2f98d728bp+0,
      0x1.55b8108f0ec5ep+0,
      0x1.6a09e667f3bccp+0,
      0x1.7f910d768cfb0p+0,
      0x1.965fea53d6e3dp+0,
      0x1.ae89f995ad3adp+0,
      0x1.c823e074ec129p+0,
      0x1.e3437e7101344p+0
    };
    int indexes[17] = { 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11 };
    

    我试过这个,它使用OP中的循环将总计算时间从大约0.5秒(对于log2f)降低到大约0.15秒。参考表的总大小为164字节。

答案 1 :(得分:2)

为了简洁和清晰而不是fk而编写z,你的内循环计算ceil(12 * log2f((f - 0.5) / z))。现在12 * log2f((f - 0.5) / z) = 12*log2f(f - 0.5) - 12*log2f(z)。事先计算一个包含999个条目的数组,允许您只用11998个对数计算来计算所有值,而不是10988001个:

for (int z=1; z<1000; ++z)
  z12[z] = 12 * log2f(z);

for (int f=1; f<11000; ++f) {
  w = 12 * log2f(f - 0.5);
  for (int z=1; z<1000; ++z) {
    int k = ceil(w - z12[z]);
  }
} 

答案 2 :(得分:1)

我找到了:

  • 虽然您有(f, fk)
  • 的11Kx1K = 11M组合
  • k的非重复值的数量仅为294.(您只需要9位来表示它)。

所以你肯定可以构造一个2d数组来存储映射并将其加载到内存中。它看起来像

LOOKUP[f][fk] = v, f in 1..11000, fk in 1..1000 
--------------------
v1,1 v1,2 v1,3 ... v1,1000
v2,1 v2,2 v2,3 ... v2,1000
...    ...    ...  ...
v11000,1 , ...     v11000,1000

由于每个v都是两个字节,因此只需要11Kx1Kx2B = 22MB内存即可加载此表。没什么。

答案 3 :(得分:0)

如果循环顺序颠倒,那么k的重复值的数量会更高。在集合中只有12977个案例,其中k有“一个一个”;最长的是618。

这表明反向方法会最小化log2f调用次数 - 必须计算索引 n ,其中k(z,f + n)!= k(z,f) (并循环n个实例......)

无论如何,在最终产品中我怀疑巨大的LUT的好处。即使使用11000 + 1000大小的表的方法对我来说也不是最理想的。相反,我猜想只有11000 + 1000个整数存在一个线性或最大2度分段多项式逼近log2,由8到16个组成。

如果找到这样的方法,则多项式系数适合NEON或XXM寄存器,并允许并行实现而无需存储器访问。