在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字节,这应该很适合大多数现代缓存。

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

其他回答

这个答案是报告用接受的答案所做的测试。我在一个排序随机整数的大向量上执行了一个闭范围测试,令我惊讶的是(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的任何大小,第一个方法在我的机器上胜过第二个方法!

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.

对于任何变量范围检查:

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倍:

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

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

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

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

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

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

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