【问题标题】:efficient floating point comparison [closed]有效的浮点比较[关闭]
【发布时间】:2013-05-28 10:00:58
【问题描述】:

嗨,我有一个函数定义为

int compareAB(float A, float B)  
{  
    if(A > B) 
    {  
        return 1;  
    }
    else if(A < B)
    {
        return -1;  
    }  
    else
    {
        return 0;
    }
}

从性能工具中可以看出,在我的项目中比较上述函数需要花费太多时间。我可以改善它的运行时间吗?

我认为一种方法是取 2 个数字的差值,然后将其与零进行比较。

谢谢,

编辑:此函数用于排序比较函数以维护搜索树数据结构。现在还更正了返回值。

【问题讨论】:

  • 该功能可能比其他功能更快。它变慢的原因是浮点数通常是 NaN 或无穷大,因为在这些情况下会显着提高运行时间。
  • 从我的项目总时间 2000 秒开始,这个函数中的 微指令需要 38 秒。而且它永远不是 NaN 或无穷大。
  • 如果时间太长,请不要这样做。改变上层算法
  • 现代处理器上 2000 秒的 CPU 时间总共是大约 4000-60000 亿条指令。 38 秒大约是 5% 的时间。您的代码中还发生了什么?是否有可能“高测量”只是该指令是“一切都聚集在一起的地方”,换句话说,处理器必须“停在这里等待其他东西的结果”?如果是这样,可能是之前的(一组)指令导致了阻塞......
  • 孤立地改进这样一个简单的功能不太可能。如果你在大程序中展示它的上下文,更有启发性和启发性的 cmets 可能是可能的。到目前为止提供的信息几乎无法提供有意义的帮助。

标签: c++ c performance floating-point


【解决方案1】:

这看起来像是一种试图规避“不应该比较浮点上的相等性”规则的人为方式。比较不等式与比较等式并没有太大区别,因为在这两种情况下都隐含地依赖浮点精度。您最后的“else”语句是隐含的 A == B。

正常的成语是if (::fabs(A - B) &lt; e),其中 e 是一些容差,尽管在您的情况下您不需要::fabs

如果您想要正、负和相等的不同结果(在计算精度的限制内),请执行类似的操作

if (A - B > e){
    return 0;
} else if (A - B < -e){
    return 1;
} else {
    return -1;
}

最好的结果是将 e 设置为 std::numeric_limits&lt;double&gt;::epsilon()。实际值取决于为达到 A 和 B 所执行的计算步骤数。1e-08 可能是现实的。

至于速度,不幸的是:我看不出这是瓶颈或运行速度更快。

【讨论】:

  • 这个答案很垃圾,因为:(a)反对比较浮点相等性的建议不是规则,只是经常重复的错误建议。 (b) 这段代码看起来不像是规避建议的人为方式。 (c) 将 fabs(A-B) 与公差进行比较可能很常见,但这并不正常。 (d) 没有使用 epsilon 作为容差的基础。大于 2 的数字永远不会有这样的差异,而小得多的数字可能会有小得多的差异。
  • (g) 使用容差进行比较会将一些结果更改为其他结果(例如,将返回 0 的情况变为返回 -1 的情况)。但是,如果不了解应用程序,就无法说明这是改进还是有害。此更改可能会将有用的结果转化为不正确和有害的结果。
  • @Bathsheba:不,我不会提供不恰当的答案。正如我在对该问题的评论中所写的那样,没有足够的信息来提供有用的建议,除了说几乎没有什么可以加快该功能。此时进行的正确方法是让提问者提供更多信息。
  • 这个解决方案的问题是它破坏了排序。如果a == bb == c,任何合理的排序算法都会得出a == c 的结论。然而,当使用建议的函数时,当abbc 的差异分别小于ε,而ac 没有差异时,情况并非如此。跨度>
  • @ydroneuad:现在已经将信息添加到问题中,我们看到这实际上是== 非常好的情况之一。对数字进行排序应该使用朴素的比较(无公差)。使用容差可能会破坏排序例程。
【解决方案2】:

这里的问题之一是返回值完全不正确。第一个返回值应该是 1,第二个应该是 -1,第三个应该是 0。如果您使用它进行排序,则排序可能不稳定,这会增加其运行时间。

原则上您可以直接返回a-b,除非您要与NaNs 打交道。

【讨论】:

  • 可能是程序使用了一种本地排序算法,该算法期望引用的函数返回的值。但是,这可能意味着有机会减少比较次数。
【解决方案3】:

浮点比较可能比普通整数比较更昂贵,尤其是在您没有专用的浮点硬件的情况下。

幸运的是,在比较浮点数时可以使用整数比较,在某些情况下:

1) 这仅在使用 IEEE754 浮点格式时有效。 2) 它不适用于 NaN:s。

访问底层表示是欠完善的行为,因为 C 语言没有指定它使用哪种浮点格式。

无论如何,诀窍在于它只适用于浮点数具有相同符号的情况。而且,在这种情况下,比较两个浮点数的整数表示是比较浮点数本身的

我没有对下面的代码进行性能测量,但它可能比您的原始代码更快。请让我知道性能提升(如果有)!

int compareAB(float a_orig, float b_orig)
{
  /* Get the underlying representation of A and B. */
  long a = *(unsigned long *)&a_orig;
  long b = *(unsigned long *)&b_orig;

  if (a < 0)
  {
    if (b < 0)
    {
      /* Both are negative. The order of the integer representation is
       * the OPPOSITE of the order of the floating-point value. */
      if (a > b)
      {
        return -1;
      }
      else if (a < b)
      {
        return 1;
      }
      else
      {
        return 0;
      }
    }
    else
    {
      /* A is negative, B isn't => A is smaller. */
      return -1;
    }
  }
  else if (b < 0)
  {
    /* B is negative, A isn't => B is smaller. */
    return 1;
  }
  else
  {
    /* Both are positive. */
    if (a > b)
    {
      return 1;
    }
    else if (a < b)
    {
      return -1;
    }
    else
    {
      return 0;
    }
  }
}

您可以使用以下方法进行测试:

#include <stdio.h>

float values[] = {-100.0F,
                  -50.0F,
                  0.0F,
                  50.0F,
                  100.0F };

void test(float a, float b)
{
  const char * p = 0;
  printf("%f is ", a);

  switch (compareAB(a, b))
  {
  case -1: p = "smaller than"; break;
  case  0: p = "equal to"; break;
  case  1: p = "greater than"; break;
  }

  printf("%s %f\n", p, b);
}

int main(void)
{
  int i;

  for (i = 0; i < sizeof(values)/sizeof(values[0]); ++i)
  {
    int j;
    for (j = 0; j < sizeof(values)/sizeof(values[0]); ++j)
    {
      test(values[i], values[j]);
    }
  }
}

它提供与使用原始代码时相同的输出,即:

-100.000000 is equal to -100.000000
-100.000000 is smaller than -50.000000
-100.000000 is smaller than 0.000000
-100.000000 is smaller than 50.000000
-100.000000 is smaller than 100.000000
-50.000000 is greater than -100.000000
-50.000000 is equal to -50.000000
-50.000000 is smaller than 0.000000
-50.000000 is smaller than 50.000000
-50.000000 is smaller than 100.000000
0.000000 is greater than -100.000000
0.000000 is greater than -50.000000
0.000000 is equal to 0.000000
0.000000 is smaller than 50.000000
0.000000 is smaller than 100.000000
50.000000 is greater than -100.000000
50.000000 is greater than -50.000000
50.000000 is greater than 0.000000
50.000000 is equal to 50.000000
50.000000 is smaller than 100.000000
100.000000 is greater than -100.000000
100.000000 is greater than -50.000000
100.000000 is greater than 0.000000
100.000000 is greater than 50.000000
100.000000 is equal to 100.000000

【讨论】:

  • 浮点数的表示就是所谓的“实现定义的”。如果您访问它,未定义的行为将允许编译器使恶魔飞出您的鼻子。事实上,编译器不允许这样做。另一方面,*(unsigned long *)&amp;a_orig 未定义的行为并允许编译器让恶魔飞出你的鼻子,因为它打破了“严格别名”:stackoverflow.com/questions/98650/…。应使用memcpy(),或 C99TC3(参见脚注 82)或 C11 允许的联合。
  • 另外,OP 在评论中提到了ucomiss。您的函数绝对不会比编译成几条ucomiss 指令的 OP 原始代码快。
猜你喜欢
  • 2022-01-18
  • 1970-01-01
  • 1970-01-01
  • 2021-05-04
  • 1970-01-01
  • 2011-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多