潜在的长循环和内部声明变量

Potentially long loop and declaring variables inside

本文关键字:内部 声明 变量 循环      更新时间:2023-10-16

我最近写了一个动态程序,可以计算两个DNA链序列之间的相似性(修改的编辑距离)(可能很长)。

我的代码是这样的(不是实际的代码,因为它是一个任务):

while(!file.eof){
   string line;
   int sizeY, sizeX;
   //get first strand
   getline(db, line)
   //second strand
   getline(db, line)
   double ** ary = new double[sizeY];
   //loop to initialize array
   for(i to sizeY)
   {
      for(i to sizex)
      {
            pair<string,string> p,d;
            p.first = "A";
            p.second = "T";
            d.first = "G";
            d.second = "C";
            //do some comparisons
      }
   }
}

上面的代码大约需要40分钟才能在大约2400行的文件中完成。如果我将p、d和赋值对移到嵌套的for循环之外,并运行完全相同的文件,它将在大约1分钟内完成。

我在其他线程中读到性能基本相同。我还用-O2编译了它。

为什么上面的代码慢得多?

考虑内部循环的每次迭代中必须发生的各种分配/解除分配。

  1. 在堆栈上分配一对对象
  2. 分配四个空字符串,每个字符串可能在堆上分配一个1字节
  3. 四个字符串分配,可能需要4个堆释放和新的分配
  4. 破坏涉及4个堆释放的字符串
  5. 对对象的破坏

忽略堆栈分配(应该相对便宜),即总共8个堆分配和另外8个释放(或者4/4的最佳情况)。如果这是一个调试构建,那么在检查每个堆操作时可能会有额外的开销。

如果您的sizeX/sizeY常量为2400,那么您总共要执行92000000次堆操作。如果幸运的话,每一个操作都需要大约相同的时间,因为每个循环都分配了相同大小的对象。如果运气不好,那么由于堆碎片,一些堆操作可能需要更长的时间才能完成。

正如您所发现的,显而易见的解决方案是将变量定义和赋值放在循环之外。您只需要在循环中某个时刻覆盖对值时重新分配对值。

一般答案:看起来你在使用gcc(也就是说g++);你总是可以做g++-S[stuff]来看看g++对你的代码做了什么(假设你能很好地阅读汇编)。

具体答案:我很惊讶差异是40倍,但在你的代码中,每次你完成一个循环,它必须调用create_new_pair两次(我本以为它必须做一点清理才能"释放"旧对,但考虑到它在堆栈上,我想这并不像我想象的那么难,或者至少我没有看到它……从Gcc读取代码过去比现在读C++代码容易得多)

这可能是因为变量是一个对象。由于p和d不是基元类型,除非编译器内联它的构造函数和析构函数(如果使用-O3而不是-O2,则可能发生这种情况),否则它将在每次迭代中构造和析构两个std::对(以及四个std::字符串)。如果它是一个基元变量(如int),即使您没有启用内联优化,编译器也可以对此进行优化。

编辑:请注意,由于std::string在内部使用堆分配,所以即使是内联也不会优化这些分配(但内联仍会节省一些开销)。对于std::对int和-O3,循环内外的性能应该相同。

在一种编译过的语言中,如果有一个好的编译器(至少可以进行平庸的优化),在循环中声明变量永远不会是"失败者",而且通常(尤其是对于只有适度优化的编译器)会是"胜利者"。

不过,对于解释语言来说,情况可能会有所不同。每次通过循环,解释器都需要分配变量位置,这可能会很昂贵。

在编译环境设计不佳的情况下,在堆栈上分配变量的成本很高,也可能出现"失败者"的情况。

尽管在任何一种情况下,我都无法解释40:1的差异。我怀疑省略的代码可能包含一些重要的线索。

[啊,在重读(可能还有海报的重新编辑)时,我发现这不仅仅是变量DECLARATION,而是OBJECT的创建被移出了循环。]