是否有一个工具/解决方案来编程一个循环,其中只在每次X迭代检查条件?

时间:2014-11-08 17:39:24

标签: c++ c loops optimization

例如:我有一个由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条件和增量数千次将是有用的,但是非常不切实际 - 所以我的问题是:

是否有一个工具(或我缺少的技术)为我做这个,但保持代码清晰,易于修改?

提前致谢!

2 个答案:

答案 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今天非常重要,因此展开太多可能会降低代码速度,另请参阅GCCthis中的__builtin_prefetch

您还可以考虑meta-programmingmulti-staged programming设施;在Meta OcamlCommon 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是一个基本的编译器优化;所以在使用这样一个奇怪的工具之前,请检查你的编译器是否已经不适合你...