C++ 快速可分性测试(2,3,4,5,…,16)?

C++ 快速可分性测试(2,3,4,5,…,16)?,c++,c,math,assembly,bit-manipulation,C++,C,Math,Assembly,Bit Manipulation,什么是最快的可分性测试?比如说,给定一个小小的endian结构和一个32位有符号整数:如何快速计算一个数字可以被2,3,4,5整除,。。。最多16岁 警告:给出的代码仅为示例。每条线都是独立的!很明显,在许多没有DIV硬件(像许多ARM一样)的处理器上,使用模运算的解决方案速度很慢。有些编译器也无法进行这样的优化(比如,如果除数是函数的参数或依赖于某个参数) 和特殊情况: Divisible_by_2k = if(number & (tk-1)) do(); //tk=2**k=(2*

什么是最快的可分性测试?比如说,给定一个小小的endian结构和一个32位有符号整数:如何快速计算一个数字可以被2,3,4,5整除,。。。最多16岁

警告:给出的代码仅为示例。每条线都是独立的!很明显,在许多没有DIV硬件(像许多ARM一样)的处理器上,使用模运算的解决方案速度很慢。有些编译器也无法进行这样的优化(比如,如果除数是函数的参数或依赖于某个参数)

和特殊情况:

Divisible_by_2k = if(number & (tk-1)) do();  //tk=2**k=(2*2*2*...) k times

整除性的快速测试在很大程度上取决于数字表示的基数。在基数为2的情况下,我认为你们只能做“快速测试”,测试2的幂的可除性。当二进制数的最后n个二进制位数为0时,该二进制数可被2n整除。对于其他测试,我不认为在任何情况下都能找到比
%

更快的测试(包括可被2整除):

使用低阶位掩码进行编译只是一种模糊处理,使用现代编译器不会比以可读方式编写代码快多少

如果您必须测试所有案例,您可以通过将一些案例放入另一个案例的
If
中来提高性能:例如,如果2的可除性已经失败,那么测试4的可除性就没有意义了。

您应该只使用(i%N)==0作为测试

我的编译器(gcc的一个相当旧的版本)为我尝试的所有情况生成了良好的代码。 如果bit测试是合适的,它就这样做了。当N是一个常数时,它不会对任何情况产生明显的“除法”,它总是使用一些“技巧”

只要让编译器为您生成代码,它几乎肯定会比您更了解机器的体系结构:)这些都是简单的优化,您不可能想出比编译器更好的东西


这是一个有趣的问题。我不能列出编译器对每个常量使用的技巧,因为我必须在不同的计算机上编译。。但是,如果没有人比我更了解这个问题,我会在以后更新这个答复:)

有点开玩笑,但假设你得到了其余的答案:

Divisible_by_6  = Divisible_by_3 && Divisible_by_2;
Divisible_by_10 = Divisible_by_5 && Divisible_by_2;
Divisible_by_12 = Divisible_by_4 && Divisible_by_3;
Divisible_by_14 = Divisible_by_7 && Divisible_by_2;
Divisible_by_15 = Divisible_by_5 && Divisible_by_3;
如前所述,让编译器为您简化它。如果
n
是一个常数,任何下降编译器都能够识别模式并将其更改为更有效的等效模式

例如,代码

#include <stdio.h>

int main() {
    size_t x;
    scanf("%u\n", &x);
    __asm__ volatile ("nop;nop;nop;nop;nop;");
    const char* volatile foo = (x%3 == 0) ? "yes" : "no";
    __asm__ volatile ("nop;nop;nop;nop;nop;");
    printf("%s\n", foo);
    return 0;
}
翻译回C代码,意思是

(hi64bit(x * 0xaaaaaaaaaaaaaaab) / 2) * 3 == x ? "yes" : "no"
// equivalatent to:                 x % 3 == 0 ? "yes" : "no"
这里不涉及分裂。(请注意,
0xaaaaaaab==0x200000000001l/3


编辑:

  • 魔法常数0xaaaaaaaaab可以在中计算
  • 对于形式为2n-1的除数,请检查

你可以用乘法代替非二次方常数的除法,本质上就是用除数的倒数相乘。用这种方法得到精确结果的细节是复杂的

在第10章中详细讨论了这一点(遗憾的是,无法在线获得)


从商中,你可以通过另一次乘法和减法得到模。

以下是一些我还没有看到其他人建议的技巧:

一种方法是使用
switch
语句,或预计算某个数组。然后,任何合适的优化器都可以直接索引每个案例。例如:

// tests for (2,3,4,5,6,7)
switch (n % 8)
{
case 0: break;
case 1: break;
case 2: do(2); break;
case 3: do(3); break;
case 4: do(2); do(4) break;
case 5: do(5); break;
case 6: do(2); do(3); do(4); break;
case 7: do(7); break;
} 

您的应用程序有点含糊不清,但您可能只需要检查小于n=16的素数。这是因为所有的数字都是当前或以前素数的因子。因此,对于n=16,您可能只需要以某种方式检查
2、3、5、7、11、13即可。只是想一想。

这些数字的LCM似乎是720。它非常小,因此您可以执行单模运算,并将余数用作预计算LUT中的索引。

在a中,我展示了一种快速算法,用于检查基数N中的因子N-1。2的不同幂之间的基变换是微不足道的;这只是一点分组

因此,在基数4中检查3很容易;在基数16中检查5很容易,在基数64中检查7(和9)很容易


非素数因子是微不足道的,所以只有11和13是硬的情况。对于11,您可以使用基数1024,但在这一点上,它对于小整数来说并不是很有效。

找出除法指令(包括x86/x64上的模)的替代方案并不是一个坏主意,因为它们非常慢。比大多数人意识到的要慢(甚至慢得多)。那些建议使用“%n”,其中n是一个变量的人给出了愚蠢的建议,因为它总是会导致使用除法指令。另一方面,“%c”(其中c是常数)将允许编译器确定其指令库中可用的最佳算法。有时是除法指令,但很多时候不是

在Torbjörn Granlund中,无符号32位mults:div所需的时钟周期比在Sandybridge上为4:26(6.5倍),在K10上为3:45(15倍)。对于64位,各自的比率为4:92(23倍)和5:77(14.4倍)

“L”列表示延迟。“T”列表示吞吐量。这与处理器并行处理多条指令的能力有关。Sandybridge可以每隔一个周期发出一个32位乘法或每一个周期发出一个64位乘法。对于K10,相应的吞吐量是相反的。对于分区,K10需要完成整个序列,然后才能开始另一个序列。我想桑迪布里奇也是这样

以K10为例,这意味着在32位除法(45)所需的周期内,可以发出相同数量(45)的乘法,并且下一个和最后一个乘法将在除法完成后完成一个和两个时钟周期。许多工作可以在45次乘法中完成

还值得注意的是,div的效率随着时间的推移而降低
(hi64bit(x * 0xaaaaaaaaaaaaaaab) / 2) * 3 == x ? "yes" : "no"
// equivalatent to:                 x % 3 == 0 ? "yes" : "no"
// tests for (2,3,4,5,6,7)
switch (n % 8)
{
case 0: break;
case 1: break;
case 2: do(2); break;
case 3: do(3); break;
case 4: do(2); do(4) break;
case 5: do(5); break;
case 6: do(2); do(3); do(4); break;
case 7: do(7); break;
} 
def mod_15ish(unsigned int x) {
  // returns a number between 0 and 21 that is either x % 15
  // or 15 + (x % 15), and returns 0 only for x == 0
  x = (x & 0xF0F0F0F) + ((x >> 4) & 0xF0F0F0F);
  x = (x & 0xFF00FF) + ((x >> 8) & 0xFF00FF);  
  x = (x & 0xFFFF) + ((x >> 16) & 0xFFFF);
  // *1
  x = (x & 0xF) + ((x >> 4) & 0xF);
  return x;
}

def Divisible_by_15(unsigned int x) {
  return ((x == 0) || (mod_15ish(x) == 15));
}
if n & 1 == 0:
    div2 = true
    if n & 3 == 0: 
        div4 = true
        if n & 7 == 0: 
            div8 = true
            if n & 15 == 0:
                div16 = true
if (flags & (div2+div3)) == (div2 + div3): do_6()
if (flags & 3) == 3: do_6()
def mod(n,m):
    i = 0
        while m < n:
            m <<= 1
            i += 1
        while i > 0:
            m >>= 1
            if m <= n: n -= m
            i -= 1
     return n

div3 = mod(n,3) == 0
...
number = bn*2^n+...+b2*4+b1*2+b0
number%3 =3= bn*(2^n % 3)+...+b2*1+b1*2+b0
ni = (n >> i) & 1;

Congruence_by_1 = 0
Congruence_by_2 = n&0x1
Congruence_by_3 = n0-n1+n2-n3+n4-n5+n6-n7+n8-n9+n10-n11+n12-n13+n14-n15+n16-n17+n18-n19+n20-n21+n22-n23+n24-n25+n26-n27+n28-n29+n30-n31
Congruence_by_4 = n&0x3
Congruence_by_5 = n0+2*n1-n2-2*n3+n4+2*n5-n6-2*n7+n8+2*n9-n10-2*n11+n12+2*n13-n14-2*n15+n16+2*n17-n18-2*n19+n20+2*n21-n22-2*n23+n24+2*n25-n26-2*n27+n28+2*n29-n30-2*n31
Congruence_by_7 = n0+2*n1+4*n2+n3+2*n4+4*n5+n6+2*n7+4*n8+n9+2*n10+4*n11+n12+2*n13+4*n14+n15+2*n16+4*n17+n18+2*n19+4*n20+n21+2*n22+4*n23+n24+2*n25+4*n26+n27+2*n28+4*n29+n30+2*n31
Congruence_by_8 = n&0x7
Congruence_by_9 = n0+2*n1+4*n2-n3-2*n4-4*n5+n6+2*n7+4*n8-n9-2*n10-4*n11+n12+2*n13+4*n14-n15-2*n16-4*n17+n18+2*n19+4*n20-n21-2*n22-4*n23+n24+2*n25+4*n26-n27-2*n28-4*n29+n30+2*n31
Congruence_by_11 = n0+2*n1+4*n2+8*n3+5*n4-n5-2*n6-4*n7-8*n8-5*n9+n10+2*n11+4*n12+8*n13+5*n14-n15-2*n16-4*n17-8*n18-5*n19+n20+2*n21+4*n22+8*n23+5*n24-n25-2*n26-4*n27-8*n28-5*n29+n30+2*n31
Congruence_by_13 = n0+2*n1+4*n2+8*n3+3*n4+6*n5-n6-2*n7-4*n8-8*n9-3*n10-6*n11+n12+2*n13+4*n14+8*n15+3*n16+6*n17-n18-2*n19-4*n20-8*n21-3*n22-6*n3+n24+2*n25+4*n26+8*n27+3*n28+6*n29-n30-2*n31
Congruence_by_16 = n&0xF
Congruence_by_1 = 0
Congruence_by_2 = n&0x1
Congruence_by_3 = (n0+n2+n4+n6+n8+n10+n12+n14+n16+n18+n20+n22+n24+n26+n28+n30)-(n1+n3+n5+n7+n9+n11+n13+n15+n17+n19+n21+n23+n25+n27+n29+n31)
Congruence_by_4 = n&0x3
Congruence_by_5 = n0+n4+n8+n12+n16+n20+n24+n28-(n2+n6+n10+n14+n18+n22+n26+n30)+2*(n1+n5+n9+n13+n17+n21+n25+n29-(n3+n7+n11+n15+n19+n23+n27+n31))
Congruence_by_7 = n0+n3+n6+n9+n12+n15+n18+n21+n24+n27+n30+2*(n1+n4+n7+n10+n13+n16+n19+n22+n25+n28+n31)+4*(n2+n5+n8+n11+n14+n17+n20+n23+n26+n29)
Congruence_by_8 = n&0x7
Congruence_by_9 = n0+n6+n12+n18+n24+n30-(n3+n9+n15+n21+n27)+2*(n1+n7+n13+n19+n25+n31-(n4+n10+n16+n22+n28))+4*(n2+n8+n14+n20+n26-(n5+n11+n17+n23+n29))
// and so on
Divisible_by_i = (Congruence_by_i == 0);
Divisible_by_6 = Divisible_by_3 && Divisible_by_2;
Divisible_by_10 = Divisible_by_5 && Divisible_by_2;
Divisible_by_12 = Divisible_by_4 && Divisible_by_3;
Divisible_by_14 = Divisible_by_7 && Divisible_by_2;
Divisible_by_15 = Divisible_by_5 && Divisible_by_3;
mod3 = pop(x & 0x55555555) + pop(x & 0xaaaaaaaa) << 1;  // <- one term is shared!
mod5 = pop(x & 0x99999999) + pop(x & 0xaaaaaaaa) << 1 + pop(x & 0x44444444) << 2;
mod7 = pop(x & 0x49249249) + pop(x & 0x92492492) << 1 + pop(x & 0x24924924) << 2;
modB = pop(x & 0x5d1745d1) + pop(x & 0xba2e8ba2) << 1 + 
       pop(x & 0x294a5294) << 2 + pop(x & 0x0681a068) << 3;
modD = pop(x & 0x91b91b91) + pop(x & 0xb2cb2cb2) << 1 +
       pop(x & 0x64a64a64) << 2 + pop(x & 0xc85c85c8) << 3;
      mod3 mod3 mod5 mod5 mod5 mod7 mod7 mod7 modB modB modB modB modD modD modD modD
mask  0x55 0xaa 0x99 0xaa 0x44 0x49 0x92 0x24 0xd1 0xa2 0x94 0x68 0x91 0xb2 0x64 0xc8
shift  *1   *2   *1   *2   *4   *1   *2   *4   *1   *2   *4   *8   *1   *2   *4   *8
sum   <-------> <------------> <----------->  <-----------------> <----------------->
bool divisible_by_2 = number % 2 == 0;
bool divisible_by_3 = number * 2863311531u <= 1431655765u;
bool divisible_by_4 = number % 4 == 0;
bool divisible_by_5 = number * 3435973837u <= 858993459u;
bool divisible_by_6 = divisible_by_2 && divisible_by_3;
bool divisible_by_7 = number * 3067833783u <= 613566756u;
bool divisible_by_8 = number % 8 == 0;
bool divisible_by_9 = number * 954437177u <= 477218588u;
bool divisible_by_10 = divisible_by_2 && divisible_by_5;
bool divisible_by_11 = number * 3123612579u <= 390451572u;
bool divisible_by_12 = divisible_by_3 && divisible_by_4;
bool divisible_by_13 = number * 3303820997u <= 330382099u;
bool divisible_by_14 = divisible_by_2 && divisible_by_7;
bool divisible_by_15 = number * 4008636143u <= 286331153u;
bool divisible_by_16 = number % 16 == 0;