首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >避免调用floor()

避免调用floor()
EN

Stack Overflow用户
提问于 2010-03-01 03:24:29
回答 9查看 11.2K关注 0票数 11

我正在编写一段代码,其中我需要处理不一定在0到1范围内的uvs (2D纹理坐标)。举个例子,有时我会得到一个u分量为1.2的uv。为了处理这个问题,我实现了一个包装,它通过执行以下操作来导致平铺:

代码语言:javascript
复制
u -= floor(u)
v -= floor(v)

这样做会导致1.2变成0.2,这是预期的结果。它还处理负的情况,例如-0.4变为0.6。

然而,这些对floor的调用相当慢。我已经使用英特尔VTune评测了我的应用程序,并且我花费了大量的周期来进行这种现场操作。

在对这个问题做了一些背景阅读后,我想出了以下函数,它的速度更快,但仍然有很多需要改进的地方(我仍然会招致类型转换惩罚,等等)。

代码语言:javascript
复制
int inline fasterfloor( const float x ) { return x > 0 ? (int) x : (int) x - 1; }

我见过一些使用内联汇编完成的技巧,但似乎没有一个是完全正确的,或者有任何显着的速度提高。

有没有人知道处理这种情况的诀窍?

EN

回答 9

Stack Overflow用户

发布于 2010-03-01 05:34:18

所以你想要一个非常快的float->int转换?AFAIK >float转换很快,但至少在MSVC++上,float->int转换会调用一个小的辅助函数ftol(),该函数会做一些复杂的工作来确保完成符合标准的转换。如果您不需要这样严格的转换,那么可以进行一些汇编黑客操作,假设您使用的是x86兼容的CPU。

下面是一个使用MSVC++内联汇编语法的快速浮点数到整型数向下舍入的函数(不管怎样,它应该会给你正确的想法):

代码语言:javascript
复制
inline int ftoi_fast(float f)
{
    int i;

    __asm
    {
        fld f
        fistp i
    }

    return i;
}

在64位MSVC++上,您需要一个外部.asm文件,因为64位编译器拒绝内联汇编。该函数基本上使用原始的x87浮点数指令来加载浮点数(fld),然后将浮点数存储为整数( FPU )。(警告:您可以通过直接调整CPU上的寄存器来更改此处使用的舍入模式,但不要这么做,否则会破坏很多东西,包括MSVC的sin和cos实现!)

如果您可以假设在CPU上支持SSE (或者有一种简单的方法来创建支持SSE的代码路径),您也可以尝试:

代码语言:javascript
复制
#include <emmintrin.h>

inline int ftoi_sse1(float f)
{
    return _mm_cvtt_ss2si(_mm_load_ss(&f));     // SSE1 instructions for float->int
}

...which基本上是一样的(加载浮点数,然后存储为整数),但使用的是SSE指令,它的速度要快一点。

其中之一应该涵盖昂贵的浮点数到整型的情况,并且任何整型到浮点型的转换都应该是很便宜的。很抱歉在这里专门针对微软,但这就是我做过类似性能工作的地方,我通过这种方式获得了很大的收益。如果可移植性/其他编译器是一个问题,那么您将不得不考虑其他问题,但是这些函数可能编译为两条指令,占用的时间小于5个时钟,而不是使用100+时钟的辅助函数。

票数 12
EN

Stack Overflow用户

发布于 2017-01-10 11:17:46

老问题,但我遇到了它,它让我有点抽筋,因为它没有得到令人满意的回答。

TL;DR:*不要**使用内联汇编、内部函数或任何其他给定的解决方案!相反,使用快速/不安全的数学优化进行编译(g++中的“-ffast- math -funsafe-math-optimizations -fno-math-errno”)。floor()如此慢的原因是因为如果强制转换溢出,它会改变全局状态(FLT_MAX不适合任何大小的标量整数类型),这也使得向量化变得不可能,除非您禁用严格的IEEE-754兼容性,您可能无论如何都不应该依赖它。使用这些标志进行编译会禁用问题行为。

以下是一些备注:

带有标量寄存器的

  1. 内联程序集是不可矢量化的,这在使用优化进行编译时会极大地抑制性能。它还要求将当前存储在向量寄存器中的任何相关值溢出到堆栈,并重新加载到标量寄存器中,这违背了使用SSE cvttss2si进行汇编的目的,在我的机器上,这实际上比使用编译器优化的简单for循环要慢。这很可能是因为,如果您允许编译器将整个代码块向量化在一起,那么它将更好地分配寄存器并避免管道停滞。对于像这样一小段内部依赖链很少、几乎没有寄存器溢出机会的代码,它几乎没有机会比被asm()包围的手工优化的代码做得更糟。
  2. 内联汇编是不可移植的,在Visual Studio64位版本中不受支持,并且非常难以阅读。
  3. 列出的所有其他方法都是不正确的,这可以说比速度慢更糟糕,而且它们在每种情况下都提供了边际性能改进,这并不能证明该方法的粗糙。(int)(x+16.0)-16.0非常糟糕,我甚至不会去碰它,但是你的方法也是错误的,因为它给了floor(-1)作为-2。在数学代码中包含分支也是一个非常糟糕的想法,因为它对性能非常关键,以至于标准库不能为您完成这项工作。所以你的(不正确的)方法应该看起来更像((int) x) - (x<0.0),可能有一个中间层,这样你就不必执行两次fpu移动。分支可能导致缓存未命中,这将完全抵消性能上的任何提高;此外,如果禁用了数学errno,则强制转换为int是任何floor()实现的最大剩余瓶颈。如果你/真的/不在乎为负整数获得正确的值,这可能是一个合理的近似值,但除非你非常了解你的用例,否则我不会冒险。
  4. 我尝试过使用按位强制转换和通过位掩码进行舍入,就像SUN的newlib实现在fmodf中所做的那样,但它花了很长时间才正确,并且在我的机器上速度要慢几倍,即使没有相关的编译器优化标志。很可能,他们为一些古老的CPU编写了这些代码,那里的浮点运算相对非常昂贵,并且没有向量扩展,更不用说向量转换操作了;在任何常见的AFAIK架构上都不再是这种情况。SUN也是Quake 3使用的快速反向sqrt()例程的发源地;现在大多数架构上都有相应的说明。微优化的最大陷阱之一是它们很快就会过时。
票数 12
EN

Stack Overflow用户

发布于 2010-03-01 05:23:21

您需要的操作可以使用fmod函数来表示(fmodf用于浮点数,而不是双精度数):

代码语言:javascript
复制
#include <math.h>
u = fmodf(u, 1.0f);

你的编译器很有可能会以最有效的方式来做这件事。

或者,您对最后一位的精度有多关心?你能给你的负值设定一个下限吗,比如知道它们永远不会低于-16.0的东西?如果是这样的话,像这样的东西将为你节省一个条件,如果它不是可以用你的数据可靠地分支预测的东西,那么它很可能是有用的:

代码语言:javascript
复制
u = (u + 16.0);  // Does not affect fractional part aside from roundoff errors.
u -= (int)u;     // Recovers fractional part if positive.

(在这个问题上,取决于你的数据看起来是什么样子,以及你使用的处理器,如果它们中的很大一部分是负的,但很小的一部分低于16.0,你可能会发现在执行条件整数转换之前添加16.0f会给你带来加速,因为它使你的条件是可预测的。或者,您的编译器可能正在使用条件分支以外的其他方法来执行此操作,在这种情况下,它没有什么用处;如果不测试和查看生成的程序集,就很难说。)

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2352303

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档