【问题标题】:Optimize code for large number of iterations针对大量迭代优化代码
【发布时间】:2013-04-18 09:47:46
【问题描述】:

我目前正在从事一个涉及大量迭代的项目(准确地说是 2^32)。在我的大部分计算中,我主要使用mathematica,但它无法处理那种数量的过程。有人建议我c++可以处理它,所以昨晚我学习了c++并编写了以下代码:

//old code  

代码运行良好,(我检查了较小的参数)但我已经开始运行它 4294967295 = 2^32-1 步,我认为这需要数百小时。如果有人能告诉我是否有办法优化此代码的某些部分以使其运行得更快,我将不胜感激?我对这种语言没有经验,所以我如何构造函数可能看起来很混乱。我认为我的 Ca2step 函数运行得非常有效(我可能错了),而且我认为我在主要部分中的循环正在减慢一切。我认为必须有更快的方法来完成我想要完成的工作,所以任何帮助都会很棒。 谢谢, 理查德。

======= 更新 ========

非常感谢大家,我真的很感激。好的,这对我来说都是全新的,所以我发现很难理解某些事情的含义。下面是我更新的代码。但是我觉得它仍然很慢。有人建议“并行化”,但我不知道这是什么以及我会怎么做?再次感谢理查德。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

//parameters
int a[32] = {0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0,
             1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1};
int b[32] = {1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 
             1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1};
// Create vector of vectors from arrays to be input into function.
vector<int> va (a, a + sizeof(a) / sizeof(int) );
vector<int> vb (b, b + sizeof(b) / sizeof(int) );

vector< vector<int> > ca2step (long int r, vector< vector<int> > vec)
{
    int rulearray[32] = { 0 };
    for (int pos = 31; pos >= 0; --pos){
        if (r % 2) 
            rulearray[pos] = 1;
        r /= 2;
    }
    int arraya[32] = {0};
    int arrayb[32] = {0};
    for (int i = 0; i < 32; i++) {
        arraya[i] = vec[0][i];
        arrayb[i] = vec[1][i];
    }

    vector< vector<int> > output;
    typedef int t_array[32];
    t_array vll, vl, vr, vrr, vx;

    rotate_copy(arrayb,arrayb+2,arrayb+32,vll);
    rotate_copy(arrayb,arrayb+1,arrayb+32,vl);    
    rotate_copy(arrayb,arrayb+31,arrayb+32,vr);    
    rotate_copy(arrayb,arrayb+30,arrayb+32,vrr);


    for (int i = 0; i < 32; i++) {
        vx[i] = (arraya[i] + rulearray[(31 - (vll[i] + (2 * vl[i]) 
                                           + (4 * arrayb[i]) + (8 * vr[i]) + (16 * vrr[i])))]) % 2;
    }

    output.push_back(vector<int>(arrayb, arrayb+32));
    output.push_back(vector<int>(vx, vx+32));

    return (output);

}

int caevolve ( long int r, vector< vector<int> > vector ){
    int count;
    for(int j=0; j<20; j++){ 
        //run function
        vector = ca2step(r, vector);
    }
    if (vector[0] == va || vector[1] == va) {
        count = 1;
        }
    else{
        count=0;
    }
    return (count);
}

int main ()
{
    vector< vector<int> > vinput;
    vinput.reserve(32);
    vinput.push_back(va);
    vinput.push_back(vb); 
    int counter = 0;

    for(unsigned long long int i=0;i<4294967295;i++){  //4294967295
        counter += caevolve(i, vinput);
        }

    cout<< "Counter : " << counter << endl;

    return 0;

}

【问题讨论】:

  • 以防万一:IO(例如cout,尤其是endl)也有很大的成本,应该在生产中的算法中避免。
  • ca2step 应该完成什么?我的直接反应是尽可能消除rotate_copys。至少马上,它看起来就像不理会向量,在下标中使用 % 可以避免相当多的复制。
  • 应该把if (vinput[0] == va | vinput[1] == va) {改成if (vinput[0] == va || vinput[1] == va) {吗?
  • pmr - 我会避免使用 cout,我只是用它来确保我得到正确的数据,谢谢! jerry - ca2step 根据给定规则和 2 个初始配置计算二阶元胞自动机的下一步。 timrau - 我想|是 OR 运算符吗?
  • @RichardWells -- 否。| 是按位或运算符。正如@timrau 所指出的,|| 是您想要的。

标签: c++ optimization


【解决方案1】:

除了 C++ 性能之外,您还应该考虑并行化代码并利用多核架构。在我看来,你的问题是一个典型的例子。

【讨论】:

    【解决方案2】:

    Jack 已经正确地确定了向量内的内存分配可能是一笔可观的成本。因此,将向量移出循环,只需 clear() 即可,而不是创建全新的向量。

    这将在每次迭代中为每个向量节省至少一次分配/解除分配。

    不要按值传递向量,而是使用const vector&lt;vector&lt;int&gt;&gt;&amp; 作为ca2step 的参数类型。这将为内部循环的每次迭代节省一大堆向量副本(以及内存分配和释放)。

    ca2step 内部,使用堆栈数组(可能是std::array)而不是向量。这可以节省更多的动态内存分配。 begin(arrayb) 将适用于数组和向量(而不是 arrayb.begin())。

    【讨论】:

    • 我刚刚尝试了您的第一个建议,但似乎无法使其正常工作,它返回了分段错误。我把 vinput.clear();在 for 循环的底部。对吗?
    【解决方案3】:

    仪器/配置文件并运行您的代码进行十万或一百万次迭代。确定花费大量执行时间的代码部分。尝试并提高这些部分的性能。重复。只有当您对自己无法进一步改进感到满意时,您才应该尝试运行它超过 40 亿次 次。

    【讨论】:

      【解决方案4】:

      有太多的数组访问。您需要一个预取或更多局部变量来表示这些重新取回的数组元素。缓存友好。在这里阅读

      http://www.research.scea.com/research/pdfs/GDC2003_Memory_Optimization_18Mar03.pdf

      【讨论】:

        【解决方案5】:

        将所有向量移到 ca2step 函数之外;使它们成为全局变量。使用vector::reserve() 扩展它们的大小,然后再开始push_back() 进入它们,你知道所有的大小。由于ca2step 现在可以在它外部的数组上工作,它不需要返回任何东西,所以不需要两个向量的向量;直接使用这两个向量,完成后,只需vector::clear() 他们。

        您可能还需要将循环变量类型更改为unsigned longunsigned long long

        【讨论】:

          【解决方案6】:

          这在某种程度上应该由编译器完成。在您的情况下,您应该尝试并行化您的代码。

          【讨论】:

          • 感谢您的建议,但我真的不知道那是什么?你知道有什么地方可以更好地解释这一点吗?
          【解决方案7】:

          您可以使用 LinkedList 代替向量。 LinkedList 具有更快的插入速度(向量的 push_back),因为它们永远不需要调整自己的大小,这在大量的情况下可能是一项昂贵的操作。

          【讨论】:

          • 这几乎总是错误的。 vector 试图最小化调整大小并且不会经常调整大小(尤其是在大尺寸时)。 list 但是迭代会慢很多。
          • 链表是否允许轮换?我还需要一个向量向量,因为函数 ca2step 的输出需要是两个向量(或列表或其他),所以这就是我如何从函数中获取两个输出。链表也可以吗?
          【解决方案8】:

          我认为您可以摆脱初始循环来填充规则数组,替换为 r: 上的位测试来测试第 n 位,您可以使用

          (r & (1 << nth)) ? 1 : 0 ...
          

          那么规则数组的用法可以替换为

          arraya[i] + (r & (1 << (31 - (vll[i] + (2 * vl[i]) + (4 * arrayb[i]) + (8 * vr[i]) + (16 * vrr[i])) ?  1 : 0)
          

          rotate_copy 可以与普通的旧数组一起使用:并且您可以使用它来避免大量动态内存分配,因为所有大小都是固定的。使用 typedef 强制执行此操作:

           typedef int t_array[32];
           t_array arraya, arrayb, vll, vl, vr, vrr, vx;
          
           rotate_copy(arrayb,arrayb+2,arrayb+32,vll);
           rotate_copy(arrayb,arrayb+1,arrayb+32,vl);    
           rotate_copy(arrayb,arrayb+31,arrayb+32,vr);    
           rotate_copy(arrayb,arrayb+30,arrayb+32,vrr);
          

          那么只有最终的返回值需要一个栈分配数组的副本:

            output.push_back(vector<int>(arrayb,arrayb+32));
            output.push_back(vector<int>(vx,vx+32));
          

          【讨论】:

          • 您好,感谢您的帮助!由于某种原因,我似乎无法让您的第二条线路工作?
          • 我没有编译,所以可能有错字...有什么错误信息要检查吗?
          【解决方案9】:

          感谢所有帮助,我终于在合理的时间内(大约 11 小时)完成了这项工作。只是想我会分享我的代码。在接下来的几周内,我需要运行几次,所以如果有任何其他技巧可以用来进一步缩短时间,我们将不胜感激!

              #include <iostream>
          using namespace std;
          
          bool is_equal ( int a[], int b[]){
              for (int i=0; i<32; i++ ){
                  if ( a[i] != b[i] )
                      return false;
              }
              return true;
          }
          
          int ca2step (long long int rule, int arraya[32], int arrayb[32] ){
              int count =0;
              int x[32];
              int y[32];
              for(int i=0;i<32;i++){
                  x[i] = arraya[i];
                  y[i] = arrayb[i];
              }
          
              for (int j=0; j<19;j++){
                      int arrayc[32];
                      for (int i=0; i<32; i++){
                      arrayc[i] = (x[i] + ((rule >> ( y[(i+2)%32] + (2 * y[(i+1)%32]) + 
                             (4 * y[i]) + (8 * y[(i+31)%32]) + (16 * y[(i+30)%32])) )& 1))%2;
                      }
          
                      for(int k=0;k<32;k++){ 
                          x[k] = y[k];
                          y[k] = arrayc[k];}
              }
          
              if(is_equal(y, arraya) || is_equal(y, arrayb)){
                  count++;}  
              return(count);     
          }
          
          int main (){
              int a[32] = {0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 
                           0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1};
              int b[32] = {1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1,
                           0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1};
              int counter = 0;
              for(long long int i=0;i<10000000;i++){  //4294967295
                  counter += ca2step(i, a, b);
                  }
          
              cout << counter ;
              return 0;
          }
          

          【讨论】:

            【解决方案10】:

            我路过这个帖子,看看问题所在。但已经有一段时间了。无论如何,我已经尝试使用一些位运算符和 openmp。

            我的假设:1.二进制数的处理 2.全32位

            我已将所有数组替换为单个 int,因为您的 32 宽数组仅包含 '0' 和 '1' 正好适合一个 int(4 字节)。这样做可以帮助您消除一些循环并节省内存访问。

            更新* 学习了一些新技巧,并用一些最少的汇编代码进行了更新

            #include <iostream>
            using namespace std;
            
            #define MASK 0x1F  /*last 5 bits*/
            
            unsigned int toInt(int a[32]){
                int result = 0;
                for(int i = 0; i<32;i++)  
                if(a[i]==1) result |= 1 << (31-i);
                return result;
            }
            
            inline unsigned int ror(unsigned int v,unsigned int sh){
            //rotate v to the right by sh
                asm("ror %1,%0;" :"=r"(v) : "cI"(sh), "0"(v) );
                return v;
            }
            
            unsigned int compute(unsigned int rule, unsigned int target){
                unsigned int t = rol(target,3);
                unsigned int d = 0;
                unsigned int k;
                for(int i=0;i<32;i++){
                    k  = ( t & MASK );
                    d |= ( (rule>>k) & 1 ) << (31-i) ;
                    t  =  rol(t,1);      
                }
                return d;
            }
            
            int ca2step (unsigned int rule, unsigned int a, unsigned int b ){
                unsigned int xx = a;
                unsigned int yy = b;
            
                int tmp;
                unsigned int d,tmpyy;
            
                for (int j=0; j<19;j++){
                    d = compute(rule,yy); 
                    tmpyy = xx ^ d ;
                    xx = yy;
                    yy = tmpyy;
                }
                return ( yy == a || yy == b ) ;  
            }    
            
            int main (){
            
                int a[32] = {0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 
                             0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1};
                int b[32] = {1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1,
                             0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1};
                int counter = 0;
                unsigned int aa = toInt(a);
                unsigned int bb = toInt(b);
            
                #pragma omp parallel for reduction(+:counter)
                for(unsigned int i=0;i < 0xffffffff ;i++){
                    counter += ca2step(i, aa, bb);
                }
            
                cout << counter <<"\n";
            
                return 0;
            }
            

            编译:

            g++ filename.cpp -O3 -fopenmp
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-07-10
              • 2018-05-31
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多