在C或c++中,是否有比x >= start && x <= end更快的方法来测试一个整数是否在两个整数之间?

更新:我的具体平台是iOS。这是框模糊函数的一部分,它将像素限制在给定正方形中的圆形。

更新:在尝试接受的答案后,我在一行代码上得到了一个数量级的加速,而不是正常的x >=开始&& x <=结束方式。

更新:下面是XCode汇编器的前后代码:

新方法

// diff = (end - start) + 1
#define POINT_IN_RANGE_AND_INCREMENT(p, range) ((p++ - range.start) < range.diff)

Ltmp1313:
 ldr    r0, [sp, #176] @ 4-byte Reload
 ldr    r1, [sp, #164] @ 4-byte Reload
 ldr    r0, [r0]
 ldr    r1, [r1]
 sub.w  r0, r9, r0
 cmp    r0, r1
 blo    LBB44_30

老方法

#define POINT_IN_RANGE_AND_INCREMENT(p, range) (p <= range.end && p++ >= range.start)

Ltmp1301:
 ldr    r1, [sp, #172] @ 4-byte Reload
 ldr    r1, [r1]
 cmp    r0, r1
 bls    LBB44_32
 mov    r6, r0
 b      LBB44_33
LBB44_32:
 ldr    r1, [sp, #188] @ 4-byte Reload
 adds   r6, r0, #1
Ltmp1302:
 ldr    r1, [r1]
 cmp    r0, r1
 bhs    LBB44_36

令人惊讶的是,减少或消除分支能够提供如此惊人的速度。


当前回答

只有一个比较/分支时,有一个老技巧可以做到这一点。它是否真的能提高速度还有待商榷,即使它真的能提高速度,可能也太小了,不值得注意或关心,但当你只从两次比较开始时,巨大提高的机会是相当渺茫的。代码如下:

// use a < for an inclusive lower bound and exclusive upper bound
// use <= for an inclusive lower bound and inclusive upper bound
// alternatively, if the upper bound is inclusive and you can pre-calculate
//  upper-lower, simply add + 1 to upper-lower and use the < operator.
    if ((unsigned)(number-lower) <= (upper-lower))
        in_range(number);

对于典型的现代计算机(即任何使用双补码的计算机),转换到unsigned实际上是一个nop——只是改变了相同位的看待方式。

注意,在典型情况下,您可以在(假定的)循环之外预先计算上下,因此这通常不会占用大量时间。除了减少分支指令的数量,这也(通常)改善了分支预测。在这种情况下,无论该数字低于范围的底端还是高于范围的顶端,都采用相同的分支。

至于这是如何工作的,基本思想很简单:一个负数,当被视为无符号数时,将比任何开始是正数的数都大。

在实践中,该方法将number和区间转换到原点,并检查number是否在区间[0,D]中,其中D = upper - lower。如果数字低于下界:为负数,如果高于上界:大于D。

其他回答

It's rare to be able to do significant optimizations to code on such a small scale. Big performance gains come from observing and modifying the code from a higher level. You may be able to eliminate the need for the range test altogether, or only do O(n) of them instead of O(n^2). You may be able to re-order the tests so that one side of the inequality is always implied. Even if the algorithm is ideal, gains are more likely to come when you see how this code does the range test 10 million times and you find a way to batch them up and use SSE to do many tests in parallel.

难道不可能只对整数执行位操作吗?

因为它必须在0到128之间,如果第8位被设置(2^7),它是128或更多。但是,边界情况会很麻烦,因为您需要一个包含比较。

对于任何变量范围检查:

if (x >= minx && x <= maxx) ...

使用位运算速度更快:

if ( ((x - minx) | (maxx - x)) >= 0) ...

这将把两个分支缩减为一个。

如果你关心类型安全:

if ((int32_t)(((uint32_t)x - (uint32_t)minx) | ((uint32_t)maxx - (uint32_t)x)) > = 0) ...

你可以将更多变量范围检查组合在一起:

if (( (x - minx) | (maxx - x) | (y - miny) | (maxy - y) ) >= 0) ...

这将把4个分支缩减为1个。

它比gcc中的旧版本快3.4倍:

我可以告诉你为什么这很重要。假设你正在模拟一个MMU。您必须经常确保给定的页面集存在给定的内存地址。这些小细节累积起来很快因为你总是说

这个地址有效吗? 这个地址在哪一页? 这个页面有什么权限?

只有一个比较/分支时,有一个老技巧可以做到这一点。它是否真的能提高速度还有待商榷,即使它真的能提高速度,可能也太小了,不值得注意或关心,但当你只从两次比较开始时,巨大提高的机会是相当渺茫的。代码如下:

// use a < for an inclusive lower bound and exclusive upper bound
// use <= for an inclusive lower bound and inclusive upper bound
// alternatively, if the upper bound is inclusive and you can pre-calculate
//  upper-lower, simply add + 1 to upper-lower and use the < operator.
    if ((unsigned)(number-lower) <= (upper-lower))
        in_range(number);

对于典型的现代计算机(即任何使用双补码的计算机),转换到unsigned实际上是一个nop——只是改变了相同位的看待方式。

注意,在典型情况下,您可以在(假定的)循环之外预先计算上下,因此这通常不会占用大量时间。除了减少分支指令的数量,这也(通常)改善了分支预测。在这种情况下,无论该数字低于范围的底端还是高于范围的顶端,都采用相同的分支。

至于这是如何工作的,基本思想很简单:一个负数,当被视为无符号数时,将比任何开始是正数的数都大。

在实践中,该方法将number和区间转换到原点,并检查number是否在区间[0,D]中,其中D = upper - lower。如果数字低于下界:为负数,如果高于上界:大于D。