我最近编写了一个动态程序,用于计算两个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编译了它。
为什么上面的代码要慢得多?
答案 0 :(得分:2)
考虑必须在内循环的每次迭代中发生的各种分配/解除分配。
忽略堆栈分配(应该相对便宜),总共有8个堆分配和另外8个解除分配(或4/4的最佳情况)。如果这是一个调试版本,则检查每个堆操作可能会有额外的开销。
如果你的sizeX / sizeY常量是2400,那么你总共做了 9200万堆操作。如果你很幸运,每个循环都会分配相同大小的对象,因此每个操作都需要大约相同的时间。如果你运气不好,那么由于堆碎片,一些堆操作可能需要更长的时间才能完成。
正如您所发现的,显而易见的解决方案是将变量定义和赋值放在循环之外。如果在某个时刻在循环内覆盖它们,则只需重新分配对值。
答案 1 :(得分:0)
通用答案: 看起来你正在使用gcc(也就是说g ++);你总是可以做g ++ -S [stuff]看看G ++对你的代码做了些什么(假设你可以很好地阅读汇编程序)。
具体答案: 我很惊讶差异是40倍,但在你的代码中,每次你完成一个循环,它必须调用create_new_pair两次(而且我认为它必须做一点清理才能“释放”旧的一对,但考虑到它在堆栈上,我想这并不像我想象的那么难,或者至少我没有看到它...从Gcc读取代码比阅读C ++代码要容易得多目前)
答案 2 :(得分:0)
这可能是因为变量是一个对象。由于p和d不是基本类型,除非编译器内联它的构造函数和析构函数(如果使用-O3而不是-O2可能会发生),它将构造并销毁两个std :: pair(因此四个std) :: string)每次迭代。如果它是一个原始变量(如int),即使您没有启用内联优化,编译器也可以优化它。
编辑: 请注意,由于std :: string内部使用堆分配,因此即使内联也不会优化这些分配(但您仍然可以使用内联节省一些开销)。对于带有-O3的std :: int对,在循环内部或外部的性能应该相同。
答案 3 :(得分:-1)
在具有良好编译器的编译语言(至少是平庸的优化)中,在循环内声明的变量永远不会成为“失败者”,并且经常(特别是对于只有适度优化的编译器)将是“赢家” ”
但是,对于解释型语言,它可能会有所不同。每次通过循环时,解释器都需要分配变量位置,这可能很昂贵。
你可能还有一个设计糟糕的编译环境的“失败者”情况,即在堆栈上分配变量的代价很高。
虽然在任何这些情况下,我都会无法解释40:1的差异。我怀疑省略的代码可能包含一些重要的线索。
<啊> [啊,在重新阅读时(可能还有海报的重新编辑)我发现它不仅仅是变量DECLARATION,而是在循环之外被移动的OBJECT创建。]