【发布时间】:2013-08-03 20:23:40
【问题描述】:
感谢Bit twiddling: which bit is set? 的一些非常有帮助的 stackOverflow 用户,我已经构建了我的函数(发布在问题的末尾)。
任何建议——即使是小建议——都将不胜感激。希望它能让我的代码变得更好,但至少它应该教会我一些东西。 :)
概述
此函数将至少被调用 1013 次,并且可能多达 1015 次。也就是说,这段代码很可能会运行 几个月,所以任何性能提示都会有所帮助。
此函数占程序时间的 72-77%,基于分析和在不同配置下的大约十几个运行(优化此处不相关的某些参数)。
目前该函数平均运行 50 个时钟。我不确定这可以改进多少,但我很高兴看到它在 30 中运行。
重点观察
如果在计算中的某个时刻,您可以判断将返回的值会很小(确切的值可以协商——例如,低于一百万)您可以提前中止。我只对大值感兴趣。
这是我希望节省最多时间的方法,而不是通过进一步的微优化(当然也欢迎这些!)。
性能信息
- smallprimes 是一个位数组(64 位);平均将设置大约 8 位,但可能少至 0 或多至 12。
- q 通常不为零。 (请注意,如果 q 和 smallprimes 为零,则函数会提前退出。)
- r 和 s 通常为 0。如果 q 为零,则 r 和 s 也将是;如果 r 为零,s 也将为零。
- 正如最后的评论所说,nu 通常最后是 1,所以我有一个有效的特殊情况。
- 特殊情况下的计算可能会出现溢出风险,但通过适当的建模,我已经证明,根据我的输入,这不会发生 - 所以不用担心这种情况。
- 此处未定义的函数(ugcd、minuu、star 等)已经优化;没有一个需要很长时间才能运行。 pr 是一个小数组(全部在 L1 中)。另外,这里调用的所有函数都是pure functions。
- 但如果你真的很在意... ugcd 是gcd,minuu 是最小值,vals 是尾随二进制 0 的数量,__builtin_ffs 是最左边的二进制 1 的位置,star 是 (n-1) > > vals(n-1), pr 是从 2 到 313 的素数数组。
- 目前正在 Phenom II 920 x4 上进行计算,但 i7 或 Woodcrest 的优化仍然值得关注(如果我在其他节点上获得计算时间)。
- 我很乐意回答您对函数或其组成部分的任何问题。
它实际上做了什么
为响应请求而添加。您无需阅读此部分。
输入是一个奇数 n,其中 1
如果数字能被 3 整除,则设置 smallprimes&1,如果数字能被 5 整除,则设置 smallprimes&2,如果数字能被 7 整除,则设置 smallprimes&4,如果数字能被 11 整除,则设置 smallprimes&8,依此类推。到表示 313 的最高有效位。可被素数的平方整除的数与仅可被该数整除的数的表示方式并无不同。 (实际上,可以丢弃平方倍数;在另一个函数的预处理阶段,素数
q、r 和 s 表示数字的较大因子。任何剩余的因子(可能大于数字的平方根,或者如果 s 不为零,甚至可能更小)可以通过从 n 中除出因子来找到。
以这种方式恢复所有因素后,使用代码最能解释的数学公式计算碱基数 1 strong pseudoprime。
目前的改进
- 提前退出测试。这显然可以节省工作量,因此我进行了更改。
- 适当的函数已经内联,所以
__attribute__ ((inline))什么都不做。奇怪的是,标记主要功能bases和一些带有__attribute ((hot))的助手会降低近 2% 的性能,我不知道为什么(但它可以通过 20 多次测试重现)。所以我没有做那个改变。同样,__attribute__ ((const))充其量也无济于事。我对此感到非常惊讶。
代码
ulong bases(ulong smallprimes, ulong n, ulong q, ulong r, ulong s)
{
if (!smallprimes & !q)
return 0;
ulong f = __builtin_popcountll(smallprimes) + (q > 1) + (r > 1) + (s > 1);
ulong nu = 0xFFFF; // "Infinity" for the purpose of minimum
ulong nn = star(n);
ulong prod = 1;
while (smallprimes) {
ulong bit = smallprimes & (-smallprimes);
ulong p = pr[__builtin_ffsll(bit)];
nu = minuu(nu, vals(p - 1));
prod *= ugcd(nn, star(p));
n /= p;
while (n % p == 0)
n /= p;
smallprimes ^= bit;
}
if (q) {
nu = minuu(nu, vals(q - 1));
prod *= ugcd(nn, star(q));
n /= q;
while (n % q == 0)
n /= q;
} else {
goto BASES_END;
}
if (r) {
nu = minuu(nu, vals(r - 1));
prod *= ugcd(nn, star(r));
n /= r;
while (n % r == 0)
n /= r;
} else {
goto BASES_END;
}
if (s) {
nu = minuu(nu, vals(s - 1));
prod *= ugcd(nn, star(s));
n /= s;
while (n % s == 0)
n /= s;
}
BASES_END:
if (n > 1) {
nu = minuu(nu, vals(n - 1));
prod *= ugcd(nn, star(n));
f++;
}
// This happens ~88% of the time in my tests, so special-case it.
if (nu == 1)
return prod << 1;
ulong tmp = f * nu;
long fac = 1 << tmp;
fac = (fac - 1) / ((1 << f) - 1) + 1;
return fac * prod;
}
【问题讨论】:
-
你能解释一下代码实际上试图计算什么吗?我在数论方面很糟糕,但在数论方面做得更好的人可能会看到你可以做出的一些算法改进。
-
有代表的人真的需要创建一个成熟的优化标签,为这种情况保留......
-
@slacker:是的……但不是。我提到计算
1 << tmp在这个实现中保证不会溢出,但是我编写了代码来处理这种情况。因为现在不适用该代码已被注释掉,但我将其留在原处以防我稍后对其进行概括。不过不错! -
谢谢大家!我现在已经将程序运行到了内置格式的限制。我正在进行部分重写以使其更高。
-
只是一个侧节点,目前有一个用于代码审查的 beta 堆栈交换站点,您可以查看codereview.stackexchange.com
标签: c performance optimization math bit-manipulation