【问题标题】:Finding MSB set of the maximal magnitude element in array查找数组中最大幅度元素的 MSB 集
【发布时间】:2012-11-22 15:18:30
【问题描述】:

给定一个len 类型的signed short 元素数组,它是在数组中的最大绝对值元素中找到最高有效位集的位置。例如,如果数组 L 包含 {-134, 123, 0, -890},那么 f(L) 应该返回 floor(log2(abs(-890)))+1

这是我目前的功能:

short MSBSetMaxMagnitude(const short *p, int len)
{
   unsigned int t = 0;

   while (len > 0)
   {
      t |= abs(*p);
      p++;
      len--;
   }
   if(t)
      return (short)(32 - __builtin_clz(t));
   else
      return 0;
}

但是,由于 abs() 函数需要分支,所以它有点慢。我尝试使用不带分支的 abs() 代替,但它甚至更慢,因为它包含至少 3 个算术指令。所以我希望也许有一种有效的算法可以准确地找到我需要的东西。

【问题讨论】:

  • @LMGTFY 正如我在问题中所说 - 这种方法比标准库中的 abs() 慢
  • 您没有说明您尝试了哪个无分支版本 :)
  • @larsman inline myabs {unsigned int r; int const mask = x >> sizeof(int) * 8 - 1; return r = (x + mask) ^ mask;} 但这与问题无关。我不是在寻找一个 abs(),而是在寻找一种完全避免它的算法。
  • 这段代码能做它应该做的吗?它会给出 log2(1) = 1,这似乎不对。
  • @AndreasBrinck 谢谢!我称它为 log2,但我真正的意思是在数组中的最大幅度元素中设置的最高有效位的位置。所以,应该是 ceil(log2(abs(maxElem(L))))

标签: c algorithm math optimization arm


【解决方案1】:

看到你是在ARM平台上工作的,你可以在2条指令中使用abs的如下实现:

EORS r1, r1, r1, ASR #32 (x = x ^ (x >> 32); carry_flag = sign_bit)
ADC r1, r1, #0           (add the sign_bit to x)

如果您可以容忍计算中 +/-1 的误差,请删除第二条指令;然后,你可以用 C 来表达它:

int abs_almost_exact(int x)
{
    return x ^ (x >> 32);
}

但是,更大的问题是循环。您可能会从展开中受益匪浅(因为每次迭代都不需要做太多事情):

do { // assuming len is even!
    int value1 = *p++;
    int value2 = *p++;
    value1 = abs(value1); // or replace abs by the hand-made version
    value2 = abs(value2);
    t |= value1;
    t |= value2;
    len--;
}
while (len > 0);

注意:我用do {} while 替换了while {},因为我使用的编译器(ARM 编译器)以这种方式生成更好的代码。

还请注意,当从内存(在我使用的处理器上)加载 short 变量时,ARM 有 2 个时钟周期的延迟。因此,最小展开因子为 3(但无论如何您都应该尽可能多地展开)。

哦,您的处理器是否支持从内存中读取short(半字)变量?我听说过一些非常古老的处理器无法做到这一点。如果您遇到这种情况,您应该将代码更改为一次加载 2 个值(1 个字),并使用一些位摆弄来分隔它们。

【讨论】:

  • 你知道在 ARM v5 上需要多少个时钟周期进行乘法运算吗?我有一个想法,也许可以使用乘法来找到循环中的平方(1 操作);但在那种情况下,我必须在循环之后进行一些计算。还是不确定。
  • 2 个 signed short 变量的乘法需要 1 个周期。但是你肯定不打算在之后计算平方根?!如果您不需要确切的值,右移 32 的近似值非常好(可能是最好的,因为它只需要 1 个周期)。
  • 任务只有:给定一个平方的 MSB 集,如何找到其平方根的 MSB 集? (当然,在主循环之后)。
【解决方案2】:

三个算术指令在任何现代处理器上都应该花费很少的时间。您正在执行两个算术运算和一个条件分支来管理循环和索引。缓慢可能是由于数据高速缓存未命中和循环由于指针使用和指针运算而导致编译器难以展开。

如果不查看数组中的每个元素,就无法找到依赖于数组中每个元素的值,因此目标应该是确保整个事物在扫描数组所需的时间内运行。

您可以通过替换来测试这是否是问题:

t |= abs(*p);

与 t |= *p;

如果这不是更快,我建议在手动展开循环中尝试非分支 abs 版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    • 2013-12-15
    • 1970-01-01
    • 2016-11-17
    • 1970-01-01
    • 2014-07-21
    相关资源
    最近更新 更多