为什么我的这个代码的基于堆栈的实现比递归慢得多?

Eri*_*ler 16 c++ optimization performance

我有一个树,其节点存储-1或非负整数,这是一个顶点的名称.每个顶点在树中最多出现一次.以下函数是我的代码中的瓶颈:

版本A:

void node_vertex_members(node *A, vector<int> *vertexList){
   if(A->contents != -1){
      vertexList->push_back(A->contents);
   }
   else{
      for(int i=0;i<A->children.size();i++){
          node_vertex_members(A->children[i],vertexList);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

版本B:

void node_vertex_members(node *A, vector<int> *vertexList){
   stack<node*> q;
   q.push(A);
   while(!q.empty()){
      int x = q.top()->contents;
      if(x != -1){
         vertexList->push_back(x);
         q.pop();
      }
      else{
         node *temp = q.top();
         q.pop();
         for(int i=temp->children.size()-1; i>=0; --i){
            q.push(temp->children[i]);
         }
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

由于某种原因,版本B运行的时间比版本A长得多,这是我没想到的.编译器可以做什么比我的代码更聪明?换句话说,我在做什么这么低效?同样让我感到困惑的是,如果我尝试检查版本B之类的东西是否在将它们放入堆栈之前孩子的内容是-1,它会显着减慢(几乎是3倍).作为参考,我在Cygwin中使用g ++和-O3选项.

更新:

我能够使用以下代码(版本C)匹配递归版本:

node *node_list[65536];

void node_vertex_members(node *A, vector<int> *vertex_list){
   int top = 0;
   node_list[top] = A;
   while(top >= 0){
      int x = node_list[top]->contents;
      if(x != -1){
         vertex_list->push_back(x);
         --top;
      }
      else{
         node* temp = node_list[top];
         --top;
         for(int i=temp->children.size()-1; i>=0; --i){
            ++top;
            node_list[top] = temp->children[i];
         }
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

明显的缺点是代码长度和幻数(以及相关的硬限制).而且,正如我所说,这只与A版本的性能相匹配.我当然会坚持递归版本,但我现在很满意它基本上是STL开销咬我.

Joh*_*nck 13

版本A具有一个显着优势:代码尺寸小得多.

版本B有一个显着的缺点:堆栈元素的内存分配.考虑到堆栈开始为空并且逐个推入元素.每隔一段时间,就必须为潜在的双端队列进行新的分配.这是一项昂贵的操作,每次调用函数时可能会重复几次.

编辑:这是g++ -O2 -S在Mac OS上使用GCC 4.7.3 生成的程序集,c++filt由我运行并注释:

versionA(node*, std::vector<int, std::allocator<int> >*):
LFB609:
        pushq   %r12
LCFI5:
        movq    %rsi, %r12
        pushq   %rbp
LCFI6:
        movq    %rdi, %rbp
        pushq   %rbx
LCFI7:
        movl    (%rdi), %eax
        cmpl    $-1, %eax ; if(A->contents != -1)
        jne     L36 ; vertexList->push_back(A->contents)
        movq    8(%rdi), %rcx
        xorl    %r8d, %r8d
        movl    $1, %ebx
        movq    16(%rdi), %rax
        subq    %rcx, %rax
        sarq    $3, %rax
        testq   %rax, %rax
        jne     L46 ; i < A->children.size()
        jmp     L35
L43: ; for(int i=0;i<A->children.size();i++)
        movq    %rdx, %rbx
L46:
        movq    (%rcx,%r8,8), %rdi
        movq    %r12, %rsi
        call    versionA(node*, std::vector<int, std::allocator<int> >*)
        movq    8(%rbp), %rcx
        leaq    1(%rbx), %rdx
        movq    16(%rbp), %rax
        movq    %rbx, %r8
        subq    %rcx, %rax
        sarq    $3, %rax
        cmpq    %rbx, %rax
        ja      L43 ; continue
L35:
        popq    %rbx
LCFI8:
        popq    %rbp
LCFI9:
        popq    %r12
LCFI10:
        ret

L36: ; vertexList->push_back(A->contents)
LCFI11:
        movq    8(%rsi), %rsi
        cmpq    16(%r12), %rsi ; vector::size == vector::capacity
        je      L39
        testq   %rsi, %rsi
        je      L40
        movl    %eax, (%rsi)
L40:
        popq    %rbx
LCFI12:
        addq    $4, %rsi
        movq    %rsi, 8(%r12)
        popq    %rbp
LCFI13:
        popq    %r12
LCFI14:
        ret
L39: ; slow path for vector to expand capacity
LCFI15:
        movq    %rdi, %rdx
        movq    %r12, %rdi
        call    std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&)
        jmp     L35
Run Code Online (Sandbox Code Playgroud)

这是相当简洁的,一眼就看起来相当没有"减速带".当我使用-O3编译时,我得到了一个不圣洁的混乱,展开的循环和其他有趣的东西.我现在没有时间注释版本B,但足以说它由于许多双端功能和在更多内存上涂鸦而更复杂.毫不奇怪,它变慢了.

  • 你知道如何生成和读取汇编代码吗?这应该更清楚.如果不这样做,您可以发布节点类的定义,也许有人会对它进行拍摄. (2认同)