我注意到计算机上有一件奇怪的事情。*手写除数测试比%
操作员快得多。考虑最小的示例:
* AMD锐龙Threadripper 2990WX,GCC 9.2.0
static int divisible_ui_p(unsigned int m, unsigned int a)
{
if (m <= a) {
if (m == a) {
return 1;
}
return 0;
}
m += a;
m >>= __builtin_ctz(m);
return divisible_ui_p(m, a);
}
该示例受奇数a
和限制m > 0
。但是,可以很容易地将其推广到所有a
和m
。该代码只是将除法转换为一系列的加法。
现在考虑使用以下命令编译的测试程序-std=c99 -march=native -O3
:
for (unsigned int a = 1; a < 100000; a += 2) {
for (unsigned int m = 1; m < 100000; m += 1) {
#if 1
volatile int r = divisible_ui_p(m, a);
#else
volatile int r = (m % a == 0);
#endif
}
}
...以及我计算机上的结果:
| implementation | time [secs] |
|--------------------|-------------|
| divisible_ui_p | 8.52user |
| builtin % operator | 17.61user |
因此快2倍以上。
问题:您能告诉我代码在您的计算机上如何运行吗?是否错过了GCC中的优化机会?您能否更快地执行此测试?
更新: 根据要求,这是一个最小的可复制示例:
#include <assert.h>
static int divisible_ui_p(unsigned int m, unsigned int a)
{
if (m <= a) {
if (m == a) {
return 1;
}
return 0;
}
m += a;
m >>= __builtin_ctz(m);
return divisible_ui_p(m, a);
}
int main()
{
for (unsigned int a = 1; a < 100000; a += 2) {
for (unsigned int m = 1; m < 100000; m += 1) {
assert(divisible_ui_p(m, a) == (m % a == 0));
#if 1
volatile int r = divisible_ui_p(m, a);
#else
volatile int r = (m % a == 0);
#endif
}
}
return 0;
}
gcc -std=c99 -march=native -O3 -DNDEBUG
在AMD Ryzen Threadripper 2990WX上编译
gcc --version
gcc (Gentoo 9.2.0-r2 p3) 9.2.0
UPDATE2:根据要求,可以处理任何a
和的版本m
(如果您还想避免整数溢出,则必须使用两倍于输入整数的整数类型来实现测试):
int divisible_ui_p(unsigned int m, unsigned int a)
{
#if 1
/* handles even a */
int alpha = __builtin_ctz(a);
if (alpha) {
if (__builtin_ctz(m) < alpha) {
return 0;
}
a >>= alpha;
}
#endif
while (m > a) {
m += a;
m >>= __builtin_ctz(m);
}
if (m == a) {
return 1;
}
#if 1
/* ensures that 0 is divisible by anything */
if (m == 0) {
return 1;
}
#endif
return 0;
}
评论不作进一步讨论;此对话已转移至聊天。
—
塞缪尔·柳
我还希望看到一个测试,在该测试中您实际上断言所
—
Mike Nakis
r
计算的那两个确实彼此相等。
@MikeNakis我刚刚添加了。
—
达伯勒
大多数现实生活中的用途
—
马特·蒂默曼斯
a % b
具有b
比小得多a
。通过测试用例中的大多数迭代,它们的大小相似或b
更大,并且在这种情况下,您的版本在许多CPU上可以更快。