关于c ++:确定整数是否在具有已知值集的两个整数(包括)之间的最快方法

Fastest way to determine if an integer is between two integers (inclusive) with known sets of values

在C或C++中是否有一种比EDCOX1(0)更快速的方法来测试一个整数是否在两个整数之间?

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

更新:在尝试了被接受的答案之后,我在一行代码上得到了一个数量级的加速,超过了正常的x >= start && x <= end方式。

更新:下面是Xcode汇编程序的前后代码:

新途径

1
2
3
4
5
6
7
8
9
10
11
// 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

老路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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

非常令人惊讶的是,减少或消除分支可以提供如此惊人的速度。


只有一个比较/分支可以做到这一点。它是否真的会提高速度可能是个问题,即使它确实提高了,也可能是太少的注意或关心,但当你仅仅从两个比较开始时,一个巨大的改进的机会是相当遥远的。代码如下:

1
2
3
4
5
6
// 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);

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

注意,在典型情况下,您可以在一个(假定的)循环外预先计算upper-lower,这样通常不会占用任何重要的时间。随着分支指令数量的减少,这也(通常)改进了分支预测。在这种情况下,无论数字是低于范围的底端还是高于范围的顶端,都采用相同的分支。

至于这是如何工作的,基本思想非常简单:负数,当作为无符号数查看时,将大于任何以正数开始的数字。

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


很少有人能够对如此小规模的代码进行显著的优化。从更高的级别观察和修改代码可以获得很大的性能提升。您可以完全消除对范围测试的需要,或者只做它们的O(n)而不是O(n^2)。你可以重新排序测试,这样不平等的一面总是隐含的。即使算法是理想的,当您看到这个代码如何进行1000万次范围测试,并且您找到一种方法将它们成批处理,并使用SSE并行执行许多测试时,也更有可能获得收益。


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

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

如果要对非常有限的一组值执行此操作,则可以创建查阅表格。执行索引可能会更昂贵,但是如果您可以在缓存中容纳整个表,那么您可以从代码中删除所有分支,这将加快速度。

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

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


此答案将报告使用已接受答案完成的测试。我对排序后的随机整数的一个大向量进行了一个闭区间测试,令我惊讶的是,(low<=num&;num<=high)的基本方法实际上比上面接受的答案快!在带有6GB内存的HP Pavilion G6(AMD A6-3400APU)上进行了测试。下面是用于测试的核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
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;

与上述公认答案相比:

1
2
3
4
5
6
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,第一个方法胜过我机器上的第二个方法!


不可能只对整数执行按位运算吗?

因为它必须在0和128之间,如果第8位被设置为(2^7),则它是128或更多。不过,边缘情况将是一种痛苦,因为您希望进行包容性比较。