例如:我有一个由while循环组成的函数(这个函数会检查质数)
function isprime(int number){
int i=2;
int max= (int) sqrt(number)+1;
while(i<max){
if(number%i==0){
return 0;
}
i++;
}
return 1;
}
我知道这将是一个非常差的测试质数的算法,但我的问题更多地集中在循环上。目前,该函数的第四个操作是“只是”检查条件。对于更大的数字,这可能是非常多的。
(快速示例1:如果“number”是1009,那将检查while条件30次,索引30次操作,if条件检查29 * 2次。这是118次操作)
我意识到我可以在while条件下剪切和粘贴并使索引通过最大值,同时导致额外的操作,不会伪造返回值。所以,如果我切断从“if ...”到“i ++”的所有内容并粘贴三(或n)次,检查while条件只占用操作的1/10(或1 /(1 + 3n),同时创建最大+ 2 * 3(或+(n-1) )* 3)不必要的操作。
(快速示例2:如果“number”为1009,则表示检查while条件11次,索引运算33次,if条件检查33 * 2次。这是100次操作,少13次)
因为我正在尝试非常大的数字(用非专业术语来说:“条件将在非常非常非常长的时间内是假的”)所以粘贴if条件和增量数千次将是有用的,但是非常不切实际 - 所以我的问题是:
是否有一个工具(或我缺少的技术)为我做这个,但保持代码清晰,易于修改?
提前致谢!
答案 0 :(得分:5)
你的问题有点不清楚。
首先,你可以略微改变你的算法;例如递增2而不是1(因为2以上的每个素数都是奇数)。
然后,当被问到optimize时(例如,使用g++ -Wall -O2
),编译器通常会执行一些loop unrolling,就好像它已经复制了&#34; (和不断折叠)一个循环的身体几次。
使用GCC,您可以通过以下方式帮助优化器:使用__builtin_expect,例如与
#ifdef __GNUC__
#define MY_UNLIKELY(P) __builtin_expect((P),0)
#else
#define MY_UNLIKELY(P) (P)
#endif
然后你就会编码
if(MY_UNLIKELY(number%i==0))
return 0;
最后,手动优化代码可能不值得痛苦,你应该进行基准测试。 (CPU cache今天非常重要,因此展开太多可能会降低代码速度,另请参阅GCC和this中的__builtin_prefetch
。
您还可以考虑meta-programming和multi-staged programming设施;在Meta Ocaml或Common Lisp等语言中,元编程意味着比C ++ 11 template
更多。您可以在运行时考虑generating C code(然后dlopen
),或者使用libjit
之类的JIT编译库(例如,执行partial evaluation)。另请阅读J.Pitrat关于meta-combinatorial search的博客,以及eval上的wikipage。我的MELT系统表明这些技术可以(痛苦地)用于与C ++相关(MELT在运行时生成C ++代码,因此以这种方式进行元编程)。
答案 1 :(得分:3)
有一个名为Duff's Device的奇怪片段适用于您事先知道应该发生多少次迭代的情况。
例如,假设你有这个循环:
for (size_t i = 0; i != max; ++i) {
call(i);
}
使用Duff的设备,您可以将其转换为仅检查每4次迭代:
size_t i = 0;
switch (max % 4) {
case 0: do { call(i++);
case 3: call(i++);
case 2: call(i++);
case 1: call(i++);
} while (i != max);
}
它因此将手动展开与补偿结合起来,因为迭代次数可能不是您展开次数的倍数。还有更多解释here。
虽然就个人而言,我建议使用稍微清晰的格式:
// Adjust "max" to a multiple of N:
size_t const adjusted_max = (max + N - 1) / N * N;
size_t const leftover_max = max - adjusted_max;
for (size_t i = 0; i != adjusted_max; i += N) {
call(i);
call(i);
// N times
}
switch (leftover_max) {
case N-1: call(adjusted_max + N - 1);
case N-2: call(adjusted_max + N -2);
...
case 1 : call(adjusted_max);
case 0 : ;
}
请注意,您可以在实际之前或之后处理剩余物,这无关紧要。
话虽如此,Loop Unrolling是一个基本的编译器优化;所以在使用这样一个奇怪的工具之前,请检查你的编译器是否已经不适合你...