高效的浮点比较 (Cortex-A8)

Posted

技术标签:

【中文标题】高效的浮点比较 (Cortex-A8)【英文标题】:Efficient floating point comparison (Cortex-A8) 【发布时间】:2012-04-30 10:12:25 【问题描述】:

有一个很大的(~100 000)浮点点变量数组,并且有一个阈值(也是浮点点)。

问题是我必须将数组中的每个变量与阈值进行比较,但 NEON 标志传输需要很长时间(根据分析器,大约 20 个周期)。

有什么有效的方法来比较这些值吗?

注意:由于舍入误差无关紧要,我尝试了以下方法:

float arr[10000];
float threshold; 
....

int a = arr[20]; // e.g.
int t = threshold;
if (t > a) ....

但在这种情况下,我得到以下处理器命令序列:

vldr.32        s0, [r0]
vcvt.s32.f32   s0, s0
vmov           r0, s0    <--- takes 20 cycles as `vmrs APSR_nzcv, fpscr` in case of 
cmp            r0, r1         floating point comparison

由于在 NEON 上发生转换,所以无论我是通过描述的方式还是浮点数来比较整数。

【问题讨论】:

您的代码与您的问题陈述不一致 - 数据是浮点数,但您将阈值显示为 int - 您还将每个浮点数据值转换为 int - 为什么?如果您的数据是浮动的,那么阈值应该是浮动的,您应该进行浮动比较(即没有 int-float 转换)。另外,您打算如何处理大于(或小于)阈值的值(这将确定 NEON 是否合适)? 许多人放弃了 NEON,因为它比 ARM 慢,却不知道要避免什么以及如何正确编程 SIMD。根据您的确切需求,从 SIMD 开始可能不可行,或者您不知道如何使用 NEON 处理 if-else。 if-else on NEON : 1. 使用 VCnn 指令创建掩码值。 2. 在两个不同的寄存器中计算两种情况(如果、否则)的结果。 3. 使用 1 中的掩码值将两个结果与 VBnn 指令结合。 由于无论是否满足条件,您总是必须在 NEON 上计算这两种情况,如果计算非常复杂,它可能比在 ARM 上慢。如果有多个关联的 if-else,忘记 NEON。 NEON->ARM 寄存器传输总是需要 11~14 个周期。应该不惜一切代价在循环内避免这种情况。为什么即使没有舍入误差也可以非常快速地比较浮点数时使用这些类型转换?我将在单独的答案中向您展示。 【参考方案1】:

如果浮点数是 32 位 IEEE-754 并且整数也是 32 位,并且如果没有 +infinity、-infinity 和 NaN 值,我们可以通过一个小技巧将浮点数与整数进行比较:

#include <stdio.h>
#include <limits.h>
#include <assert.h>

#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1]
C_ASSERT(sizeof(int) == sizeof(float));
C_ASSERT(sizeof(int) * CHAR_BIT == 32);

int isGreater(float* f1, float* f2)

  int i1, i2, t1, t2;

  i1 = *(int*)f1;
  i2 = *(int*)f2;

  t1 = i1 >> 31;
  i1 = (i1 ^ t1) + (t1 & 0x80000001);

  t2 = i2 >> 31;
  i2 = (i2 ^ t2) + (t2 & 0x80000001);

  return i1 > i2;


int main(void)

  float arr[9] =  -3, -2, -1.5, -1, 0, 1, 1.5, 2, 3 ;
  float thr;
  int i;

  // Make sure floats are 32-bit IEE754 and
  // reinterpreted as integers as we want/expect
  
    static const float testf = 8873283.0f;
    unsigned testi = *(unsigned*)&testf;
    assert(testi == 0x4B076543);
  

  thr = -1.5;
  for (i = 0; i < 9; i++)
  
    printf("%f %s %f\n", arr[i], "<=\0> " + 3*isGreater(&arr[i], &thr), thr);
  

  thr = 1.5;
  for (i = 0; i < 9; i++)
  
    printf("%f %s %f\n", arr[i], "<=\0> " + 3*isGreater(&arr[i], &thr), thr);
  

  return 0;

输出:

-3.000000 <= -1.500000
-2.000000 <= -1.500000
-1.500000 <= -1.500000
-1.000000 >  -1.500000
0.000000 >  -1.500000
1.000000 >  -1.500000
1.500000 >  -1.500000
2.000000 >  -1.500000
3.000000 >  -1.500000
-3.000000 <= 1.500000
-2.000000 <= 1.500000
-1.500000 <= 1.500000
-1.000000 <= 1.500000
0.000000 <= 1.500000
1.000000 <= 1.500000
1.500000 <= 1.500000
2.000000 >  1.500000
3.000000 >  1.500000

当然,如果您的阈值没有改变,那么在isGreater() 中预先计算用于比较运算符的最终整数值是有意义的。

如果你害怕上面代码中C/C++中未定义的行为,你可以在汇编中重写代码。

【讨论】:

似乎是个好主意。我仍然面临 vmov.32 的问题,但主要是这是一个好主意。谢谢。 @vasile:你在说什么错误?什么是复杂的?如果是,你如何使它更简单? 我指的是@Paul-R 的回答。【参考方案2】:

如果您的数据是浮点数,那么您应该与浮点数进行比较,例如

float arr[10000];
float threshold;
....

float a = arr[20]; // e.g.
if (threshold > a) ....

否则您将进行昂贵的浮点整数转换。

【讨论】:

如果我在 2 个浮点数之间进行比较,它会导致昂贵的标志寄存器传输。这就是为什么我试图在 2 个整数之间进行比较。 当阈值测试为真/假时,您随后执行哪些操作? vcmpe.f32 s17, s16 vmrs APSR_nzcv, fpscr 如果我有你的问题。 Alex:我的意思是:在你的阈值测试之后, ... 会发生什么?这将确定使用 NEON 进行测试是否合适。 好吧,如果有很多代码,那么测试的成本应该可以忽略不计(除非绝大多数数据点不需要处理?)。【参考方案3】:

您的示例显示了编译器生成的代码有多糟糕:

它使用 NEON 加载一个值,只是为了将其转换为 int,然后执行 NEON->ARM 传输,这会导致管道刷新,从而浪费 11~14 个周期。

最好的解决方案是完全手工编写函数。

但是,有一个简单的技巧可以在不进行类型转换和截断的情况下进行快速浮点比较:

阈值正值(与 int 比较一样快):

void example(float * pSrc, float threshold, unsigned int count)

  typedef union 
    int ival,
    unsigned int uval,
    float fval
   unitype;

  unitype v, t;
  if (count==0) return;
  t.fval = threshold;
  do 
    v.fval = *pSrc++;
    if (v.ival < t.ival) 
      // your code here
    
    else 
      // your code here (optional)
    
   while (--count);

阈值负数(每个值比 int 比较多 1 个周期):

void example(float * pSrc, float threshold, unsigned int count)

  typedef union 
    int ival,
    unsigned int uval,
    float fval
   unitype;

  unitype v, t, temp;
  if (count==0) return;
  t.fval = threshold;
  t.uval &= 0x7fffffff;
  do 
    v.fval = *pSrc++;
    temp.uval = v.uval ^ 0x80000000;
    if (temp.ival >= t.ival) 
      // your code here
    
    else 
      // your code here (optional)
    
   while (--count);

我认为它比上面公认的要快得多。再说一次,我有点太晚了。

【讨论】:

【参考方案4】:

如果舍入误差无关紧要,那么您应该使用std::lrint。

Faster Floating Point to Integer Conversions 建议使用它进行浮点到整数的转换。

【讨论】:

以上是关于高效的浮点比较 (Cortex-A8)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ARM Cortex-A8 中设置特权模式?

Cortex-A8 强制内存缓存

Cortex-A8与STM32的区别

Cortex-A

Cortex-A8处理器memcpy的优化方案

ARM中Cortex-A8,Cortex-M0,Cortex-M3 他们的区别在哪?