在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

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


这取决于您希望对相同的数据执行多少次测试。

如果您只执行一次测试,可能没有有效的方法来加快算法的速度。

如果您正在为一个非常有限的值集执行此操作,那么您可以创建一个查找表。执行索引的代价可能更大,但是如果您可以将整个表放入缓存中,那么您就可以从代码中删除所有分支,这应该会加快速度。

对于您的数据,查找表将是128^3 = 2,097,152。如果可以控制这三个变量中的一个,从而同时考虑start = N的所有实例,那么工作集的大小将下降到128^2 = 16432字节,这应该很适合大多数现代缓存。

您仍然需要对实际代码进行基准测试,以查看无分支查找表是否比明显的比较更快。


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

// 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或更多。但是,边界情况会很麻烦,因为您需要一个包含比较。


这个答案是报告用接受的答案所做的测试。我在一个排序随机整数的大向量上执行了一个闭范围测试,令我惊讶的是(low <= num && num <= high)的基本方法实际上比上面接受的答案更快!测试在惠普Pavilion g6 (AMD A6-3400APU, 6GB内存)上完成。下面是用于测试的核心代码:

int num = rand();  // num to compare in consecutive ranges.
chrono::time_point<chrono::system_clock> start, end;
auto start = chrono::system_clock::now();

int inBetween1{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (randVec[i - 1] <= num && num <= randVec[i])
        ++inBetween1;
}
auto end = chrono::system_clock::now();
chrono::duration<double> elapsed_s1 = end - start;

与以下是上面公认的答案相比:

int inBetween2{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (static_cast<unsigned>(num - randVec[i - 1]) <= (randVec[i] - randVec[i - 1]))
        ++inBetween2;
}

注意,randVec是一个排序向量。对于MaxNum的任何大小,第一个方法在我的机器上胜过第二个方法!


对于任何变量范围检查:

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。您必须经常确保给定的页面集存在给定的内存地址。这些小细节累积起来很快因为你总是说

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