作为我对原始问题的一个跟进,关于这段代码的一小部分我决定跟进,看看你能做得更好,然后到目前为止我们提出的。
下面的代码遍历二叉树(左/右=子/下)。我相信这里有一个较少的条件空间(down
布尔值)。最快的答案获胜!
cnt
语句可以是多个语句,因此请确保只显示一次child()
和next()
成员函数的速度是hasChild()和hasNext()操作的30倍。目前,此代码在测试树中访问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.
}
}
}
答案 0 :(得分:5)
为什么不是递归解决方案?
void processTree (const BaseNodePtr ¤t, unsigned int & cnt )
{
cnt++;
if (current->hasChild())
processTree(current->child());
if (current->hasNext())
processTree(current->next());
}
由于shared_ptr
似乎是您的瓶颈,为什么不改进它?你在使用线程吗?如果不是,则取消定义符号BOOST_HAS_THREADS
。 shared_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 ¤t, unsigned int & cnt )
{
for(bool gotNext = true; gotNext; current = current->next()) {
cnt++;
if (current->hasChild())
processTree(current->child());
gotNext = current->hasNext();
}
}