树迭代器,你能进一步优化吗?

时间:2009-08-19 20:08:02

标签: c++ optimization iteration binary-tree

作为我对原始问题的一个跟进,关于这段代码的一小部分我决定跟进,看看你能做得更好,然后到目前为止我们提出的。

下面的代码遍历二叉树(左/右=子/下)。我相信这里有一个较少的条件空间(down布尔值)。最快的答案获胜!

  1. cnt语句可以是多个语句,因此请确保只显示一次
  2. child()next()成员函数的速度是hasChild()和hasNext()操作的30倍。
  3. 保持迭代< - 删除此要求,因为呈现的递归解决方案更快。
  4. 这是C ++代码
  5. 节点的访问顺序必须保持原样,如下例所示。 (先点击父母,然后是孩子,然后是'下一个'节点)。
  6. BaseNodePtr是一个boost :: shared_ptr,因为赋值很慢,避免任何临时的BaseNodePtr变量。
  7. 目前,此代码在测试树中访问62200000个节点需要5897ms,将此功能调用200,000次。

    void processTree (BaseNodePtr current, unsigned int & cnt )
    {
        bool down = true;
    
        while ( true )
        {
            if ( down )
            {
                while (true) {
    
                    cnt++; // this can/will be multiple statesments
    
                   if (!current->hasChild()) break;
                   current = current->child();
                }
            }
    
            if ( current->hasNext() )
            {
                down = true;
                current = current->next();
            }
            else
            {
                down = false;
                current = current->parent();
                if (!current)
                    return; // done.
            }
        }
    }
    

5 个答案:

答案 0 :(得分:5)

为什么不是递归解决方案?

void processTree (const BaseNodePtr &current, unsigned int & cnt )
{
  cnt++;

  if (current->hasChild())
    processTree(current->child());
  if (current->hasNext())
    processTree(current->next());
}

由于shared_ptr似乎是您的瓶颈,为什么不改进它?你在使用线程吗?如果不是,则取消定义符号BOOST_HAS_THREADSshared_ptr引用计数由互斥锁保护,这可能是性能降低的原因。

为什么不将您的数据结构更改为完全不使用shared_ptr?自己管理原始指针?也许使用scoped_ptr代替?

答案 1 :(得分:3)

为了最终加速,您需要做的是在内存中订购节点,以便按照您访问它们的顺序将它们存储在一个连续的块中。

例如,如果您的树定义如下。

        1
       / \
      2   3
     / \  /\
    4   5 6 7
   /\    /  /\
  8  9  10 11 12
 / \           \
13 14          15

然后,所描述的访问功能将按以下顺序访问节点

1
 2
  4
   8
    13
    14
   9
  5
 3
  6
   10
  7
   11
   12
     15

现在,如果您将内存中的节点作为15个分配的连续块进行排序,并按上面显示的顺序存储节点,那么您通常会访问具有“spatial locality”的节点。这可以提高缓存命中率,具体取决于节点结构的大小,从而使运行速度更快。

创建一个快速迭代方法,只访问树中的所有节点一次,没有递归。

unsigned int g_StackDepth = 0;
BaseNodePtr* g_Stack[MAX_STACK_DEPTH];

void processTree (BaseNodePtr root, unsigned int & cnt )
{
    g_Stack[g_StackDepth++] = root;
    while( g_StackDepth > 0 )
    {
        BaseNodePtr curr = g_Stack[--g_StackDepth];
        cnt++;

        if ( curr->HasNext() )
        {
            g_Stack[g_StackDepth++] = curr->Next();
        }


        if ( curr->HasChild() )
        {
            g_Stack[g_StackDepth++] = curr->Child();
        }

    }
}

根据上述顺序,据我所知,您应该获得最佳速度。

显然这有局限性,因为你必须知道你的堆栈有多大可能提前增长。虽然你可以通过使用std :: vector来解决这个问题。但是,使用std :: vector会消除上述迭代方法提供的所有优点。

希望这是一些帮助:)

答案 2 :(得分:1)

创建一个“nextvisit”函数,并继续调用它,以简化代码;接下来,对共享指针使用const引用iso值语义...这可以为您节省宝贵的共享ptr副本:

// define the order of visitation in here
BaseNodePtr& next( const BaseNodePtr& p ) {
    if( p->hasChild() ) return p->child();
    if( p->hasNext() ) return p->next();
    BaseNodePtr ancestor = p->parent();
    while( ancestor != 0 && !ancestor->hasNext() ) ancestor = ancestor->parent();
    return ancestor;
}

void processTree( const BaseNodePtr& p, unsigned int& cnt ) {
   while( p != NULL ) {
     ++cnt;
     p = next(p);
   }        
}

但是为了可读性,清晰度,可维护性,......出于上帝的考虑,使用递归。除非你的筹码不够大。

答案 3 :(得分:1)

HATE 当答案以“不要那样做”的方式解决问题时,我会去...

说有一种方法可以删除下来的bool ...这真的会让执行时间真正有所不同吗?我们讨论的是少量CPU操作和堆栈上的一些额外字节。

如果你需要速度,专注于让child()和parent()调用更快。否则你就是在浪费时间(IMOHO)。

编辑: 也许走在树上(带有这个“慢”代码)ONCE并按所需顺序构建一个指向树的指针数组。稍后使用此“索引”。

我所说的是我认为你正在从错误的角度进行优化。

答案 4 :(得分:1)

以下是如何只有一个递归调用而不是两个:

void processTree (const BaseNodePtr &current, unsigned int & cnt )
{
  for(bool gotNext = true; gotNext; current = current->next()) { 
    cnt++;
    if (current->hasChild())
      processTree(current->child());
    gotNext = current->hasNext();
  }
}