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

时间:2013-06-13 19:21:43

标签: c++ c performance math

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

更新:我的特定平台是iOS。这是盒子模糊功能的一部分,它将像素限制为给定方块中的圆圈。

UPDATE :在尝试accepted answer之后,我在一行代码上获得了一个数量级的加速,而不是正常的x >= start && x <= end方式。

UPDATE :这是带有来自XCode的汇编程序的after和before代码:

新方式

// 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

OLD WAY

#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

如何减少或消除分支可以提供如此惊人的速度,这真是太神奇了。

7 个答案:

答案 0 :(得分:510)

只有一个比较/分支有一个老技巧。它是否能真正提高速度可能会受到质疑,即使它确实如此,它可能太少注意或不关心,但当你只是开始两次比较时,巨大改进的可能性非常小。代码如下:

// 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);

使用典型的现代计算机(即使用二进制补码的任何东西),转换为无符号实际上是一个不必要的 - 只是改变了相同位的查看方式。

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

至于它是如何工作的,基本思路非常简单:当被视为无符号数时,负数将大于以正数开头的任何数字。

实际上,此方法会将number和间隔转换为原点,并检查number是否位于[0, D]区间D = upper - lower。如果number低于下限:否定,如果高于上限:大于D

答案 1 :(得分:17)

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

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

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

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

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

答案 2 :(得分:17)

很少能够对如此小规模的代码进行重大优化。从更高级别观察和修改代码可以获得巨大的性能提升。您可以完全消除对范围测试的需要,或者仅执行O(n)而不是O(n ^ 2)。您可以重新排序测试,以便始终隐含不平等的一面。即使算法是理想的,当您看到此代码如何进行1000万次范围测试并且您找到一种方法来批量处理并使用SSE并行执行多项测试时,更有可能获得增益。

答案 3 :(得分:2)

这个答案是报告用接受的答案进行的测试。我对一个排序随机整数的大向量进行了一个闭合范围测试,令我惊讶的是(低&lt; = num&amp;&amp; num&lt; = high)的基本方法实际上比上面接受的答案快!在HP 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,第一种方法胜过我机器上的第二种方法!

答案 4 :(得分:2)

对于任何可变范围检查:

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 times faster

enter image description here

答案 5 :(得分:-1)

我可以确切地告诉您为什么这很重要。假设您正在模拟 MMU。您必须不断确保给定的内存地址存在于给定的页面集。这些小东西加起来很快,因为你总是说

  • 这个地址有效吗?
  • 此地址属于哪个页面?
  • 此页面有哪些权利?

答案 6 :(得分:-3)

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

由于它必须在0到128之间,如果设置了第8位(2 ^ 7),则它是128或更多。然而,边缘情况将是一个痛苦,因为你想要一个包容性的比较。