C 比%运算符更快的可分性测试?

C 比%运算符更快的可分性测试?,c,math,x86,compiler-optimization,modulo,C,Math,X86,Compiler Optimization,Modulo,我注意到我的电脑上有一个奇怪的东西。*手写的可分性测试比%操作符要快得多。考虑最小的例子: *AMD Ryzen螺纹裂土器2990WX,GCC 9.2.0 static int-divisible\u-ui\p(无符号int-m,无符号int-a) { 如果(m>==内置ctz(m); 返回可整除的p(m,a); } 该示例受到奇数a和m>0的限制。但是,它可以很容易地推广到所有a和m。代码只是将除法转换为一系列加法 现在考虑测试程序用 -STD= C99 -三月=本土化- O3: for(无

我注意到我的电脑上有一个奇怪的东西。*手写的可分性测试比
%
操作符要快得多。考虑最小的例子:

*AMD Ryzen螺纹裂土器2990WX,GCC 9.2.0

static int-divisible\u-ui\p(无符号int-m,无符号int-a)
{
如果(m>==内置ctz(m);
返回可整除的p(m,a);
}
该示例受到奇数
a
m>0
的限制。但是,它可以很容易地推广到所有
a
m
。代码只是将除法转换为一系列加法

现在考虑测试程序用<代码> -STD= C99 -三月=本土化- O3:

for(无符号整数a=1;a<100000;a+=2){
用于(无符号整数m=1;m<100000;m+=1){
#如果1
volatile int r=可除的(m,a);
#否则
易失性int r=(m%a==0);
#恩迪夫
}
}
…以及我计算机上的结果:

| implementation     | time [secs] |
|--------------------|-------------|
| divisible_ui_p     |    8.52user |
| builtin % operator |   17.61user |
因此速度要快2倍以上

问题:您能告诉我代码在您的机器上的行为吗?是否错过了GCC中的优化机会?您能更快地完成此测试吗


更新: 根据要求,这里是一个最小的可复制示例:

#包括
静态整除整数(无符号整数m,无符号整数a)
{
如果(m>==内置ctz(m);
返回可整除的p(m,a);
}
int main()
{
for(无符号整数a=1;a<100000;a+=2){
用于(无符号整数m=1;m<100000;m+=1){
断言(可除的μuiμp(m,a)=(m%a==0));
#如果1
volatile int r=可除的(m,a);
#否则
易失性int r=(m%a==0);
#恩迪夫
}
}
返回0;
}
使用AMD Ryzen Threadripper 2990WX上的gcc-std=c99-march=native-O3-DNDEBUG编译

gcc --version
gcc (Gentoo 9.2.0-r2 p3) 9.2.0

更新2:根据要求,可以处理任何
a
m
的版本(如果您还想避免整数溢出,则必须使用输入整数的两倍长度的整数类型执行测试):

int可整除(无符号int m,无符号int a)
{
#如果1
/*甚至可以处理一个*/
int alpha=uuu内置ctz(a);
if(alpha){
if(内置ctz(m)>=α;
}
#恩迪夫
而(m>a){
m+=a;
m>>=内置ctz(m);
}
如果(m==a){
返回1;
}
#如果1
/*确保0可以被任何东西整除*/
如果(m==0){
返回1;
}
#恩迪夫
返回0;
}

你所做的就是所谓的强度降低:用一系列便宜的操作取代昂贵的操作

许多CPU上的mod指令速度较慢,因为它以前没有在几个常见的基准测试中进行过测试,因此设计者优化了其他指令。如果必须进行多次迭代,该算法的性能会更差,
%
在只需要两个时钟周期的CPU上性能会更好


最后,请注意,有许多快捷方式可以将剩余部分除以特定常数。(尽管编译器通常会为您处理这一问题。)

我将自己回答我的问题。我似乎成了分支预测的牺牲品。操作数的相互大小似乎并不重要,只是它们的顺序

考虑以下实现

int可整除(无符号int m,无符号int a)
{
而(m>a){
m+=a;
m>>=内置ctz(m);
}
如果(m==a){
返回1;
}
返回0;
}
还有阵列

无符号整数A[100000/2];
无符号整数M[100000-1];
for(无符号整数a=1;a<100000;a+=2){
A[A/2]=A;
}
用于(无符号整数m=1;m<100000;m+=1){
M[M-1]=M;
}
使用该函数洗牌/不洗牌的

如果不进行洗牌,结果仍然是错误的

| implementation     | time [secs] |
|--------------------|-------------|
| divisible_ui_p     |    8.56user |
| builtin % operator |   17.59user |
但是,一旦我洗牌这些数组,结果就不同了

| implementation     | time [secs] |
|--------------------|-------------|
| divisible_ui_p     |   31.34user |
| builtin % operator |   17.53user |

评论不是为了进一步的讨论;这段对话是。我还想看一个测试,在这个测试中,你实际断言你计算的两个
r
s确实是相等的。@MikeNakis我刚刚补充了这一点。
a%b
的大多数实际使用都比
a
小得多在您的测试用例中,大多数迭代都具有相似的大小,或者
b
更大,并且在这些情况下,您的版本在许多CPU上可以更快。历史上没有在几个常见的基准测试中进行过测试,这也是因为除法本身就是迭代的,很难快速进行!x86至少会将剩余部分作为
div
/
id的一部分进行测试iv
在英特尔Penryn、Broadwell和IceLake(高基硬件除法器)中获得了一些喜爱。我对“强度降低”的理解就是用一个较轻的操作替换循环中的一个较重的操作,例如,不是每次迭代都使用
x=i*const
而是每次迭代都使用
x+=const
。我不认为用移位/加法循环替换单个乘法会被称为强度降低。我说这个术语可以这样使用,但需要注意“该材料存在争议。最好将其描述为窥视孔优化和指令分配。”