C++ 面向数据的无递归树遍历

C++ 面向数据的无递归树遍历,c++,memory-management,tree,game-engine,data-oriented-design,C++,Memory Management,Tree,Game Engine,Data Oriented Design,我有一个这样的树结构:一个模型有一个根节点,每个节点有任意数量的子节点和任意数量的网格 在我的应用程序中,很多时间都花在遍历这棵树、进行视图截锥剔除和矩阵乘法等计算上。目前,它是简单实现的,每个节点都有子节点和网格的向量,并且递归遍历树。这很慢 我一直在研究面向数据的设计,我喜欢它对缓存非常友好的想法。我一直在想这样的事情: struct Mesh { // misc data MeshID mMeshID; } // probably needs more informati

我有一个这样的树结构:一个模型有一个根节点,每个节点有任意数量的子节点和任意数量的网格

在我的应用程序中,很多时间都花在遍历这棵树、进行视图截锥剔除和矩阵乘法等计算上。目前,它是简单实现的,每个节点都有子节点和网格的向量,并且递归遍历树。这很慢

我一直在研究面向数据的设计,我喜欢它对缓存非常友好的想法。我一直在想这样的事情:

struct Mesh
{
    // misc data
    MeshID mMeshID;
}

// probably needs more information?
struct Node
{
    // begin and end index into Models 'mNodes'
    uint32_t mChildrenBegin;
    uint32_t mChildrenEnd;

    // as above but for meshes
    uint32_t mMeshesBegin;
    uint32_t mMeshesEnd;
}

struct Model
{
    std::vector<Node> mNodes;
    std::vector<Mesh> mMeshes;
}
Node *curr = model.getRoot();  // TODO: handle empty tree
Node *next;
bool  shouldnt_visit_children;

while (1)
{
  // TODO: visit curr -- determine shouldnt_visit_children

  if (shouldnt_visit_children || NULL == (next = model.getChild(curr)))
  {
    // move to next sibling; if no siblings remain then ascend while no siblings remain

    while (NULL == (next = model.getNextSibling(curr)))
      if (model.getEnd() == (curr = model.getParent(curr)))
        goto DONE;

    curr = next;
  }
  else
    curr = next;  // descend to first child
}

DONE:;
struct-Mesh
{
//杂项数据
MeshID-mMeshID;
}
//可能需要更多的信息?
结构体类型
{
//开始和结束索引到模型“mNodes”
uint32\u t McChildrenBegin;
uint32_t mChildrenEnd;
//如上所述,但适用于网格
uint32_t mMeshesBegin;
uint32_t mMeshesEnd;
}
结构模型
{
std::向量mNodes;
std::向量mMeshes;
}

现在我需要遍历树以获得可见网格的列表。在每个节点上,我必须检查节点是否可见。下列分支机构:

  • 节点可见。它下面的所有子节点和网格也可见。不要深入这一分支。检查相同深度的其他节点
  • 节点不可见。此节点或其下方的子节点或网格将不可见。不要深入这一分支。检查相同深度的其他节点
  • 节点部分可见。某些节点和/或某些网格可见。必须深入到层次结构中
这棵树是静止的。一旦将模型加载到应用程序中,树就不会更改。因此,我必须能够利用这些信息获得一个有效的结构

但我不知道该如何处理这个问题

几个问题

  • 如何在内存中布局节点?是第一个索引的根节点吗?是否根据深度添加其他节点
  • 如何在不使用递归的情况下迭代树?我不想访问每个节点,除非我真的必须访问。例如,如果深度为2的节点不可见,则不应测试其所有网格和子节点(及其网格),而应完全跳过

  • 简短版本:改用6502的预先订购答案。我将在下面留下我以前的答案,因为它仍然有一些有趣的代码和注释

    以半预先订购的方式布置存储阵列。即:哨兵端节点、根、第一根的子节点、第一根的第一个子节点、第一根的第一孙子节点,等等。然后使用递归半预顺序遍历遍历树,该遍历将直接祖先及其兄弟的索引信息的副本保留在堆栈上。这样,遍历将从左到右扫描存储阵列,而不会进行回溯。您不需要使用递归访问所有节点,任何跳过子树的操作都只会在存储阵列中向前跳转扫描

    model.semiPreOrderTraversalRecur(model.getEnd());  // traverse the whole tree
    
    ...
    
    // NOTE: pass prnt by *COPY* -- break Node into index portion and app. data portion; we only need the index portions here
    
    void Model::semiPreOrderTraversalRecur(Node prnt)
    {  
      Node children[prnt.mChildrenEnd - prnt.mChildrenBegin];  // just index info
      uint32_t i;
    
      // visit children of prnt; determine visibility etc.
    
      for (i = prnt.mChildrenBegin; i < prnt.mChildrenEnd; ++i)
      {
        cout << "Visiting " << mNodes[i].mVal << endl;
        mNodes[i].mInvisible = false;  // TODO: determine based on culling, etc.
        children[i - prnt.mChildrenBegin] = mNodes[i];  // just index info
      }
    
      // recurse on children as necessary
    
      for (i = 0; i < prnt.mChildrenEnd - prnt.mChildrenBegin; ++i)
        if (!children[i].mInvisible && children[i].mChildrenBegin != children[i].mChildrenEnd)
          semiPreOrderTraversalRecur(children[i]);
    }
    
    尽管如此,我看不到任何明显的理由说明这种实现(与之前的链接结构相反)可能具有更好的缓存性能。向量的布局和访问方式将决定缓存性能。无论如何,我看不出任何令人信服的理由来解释为什么以任何特定的方式进行布局时,您不可能获得类似的结果—构建一个类似的链接树。例如,如果您发现/相信以半预排序的树遍历方式排列向量(即,向量的排列方式为:end、root、root的子对象、root的第一个子对象、root的第一孙子对象等)可以为应用程序提供最佳缓存性能,然后,使用相同的半预顺序构建顺序构建链接树很可能会产生类似的结果,因为内存分配器可能会以与显式方式类似的方式将树打包到内存中。当然,通过您的方法,您可以确定地控制这一点,而不是依赖于您的结构和相关分配器的智能

    显式地管理内存中节点和网格的布局可能会产生更好的缓存性能,因为您可以“强制”它将对象紧密地打包在内存中,并强制执行您喜欢的访问模式/位置,但是一个合适的分配器可能会获得类似的结果,尤其是如果在启动时只进行一次树构建

    如果您主要按照您的问题所建议的顺序进行预排序遍历,那么我建议您以半预排序的方式布置存储向量,如上文所述:end、root、root的子对象、root的第一个子对象、root的第一个孙子对象等等

    PS-如果遍历总是从根开始,那么您也可以消除节点中的mParentIndex,而是在遍历树时构建一个显式的祖先堆栈,以允许您在下降后遍历树(这可能是递归隐式执行的)。如果需要能够从任意给定节点在树中移动,则需要将父节点的索引存储在节点中

    编辑:正如我在一篇评论中提到的,我提出的半预排序布局还具有一个特性,即一个节点的所有子代网格都可以在一个简单的数值范围内表示[Node.mDescedantMeshBegin,Node.MDEscedantMeshEnd)当您按照建议单独存储网格时。因此,如果您需要或希望通过遍历树来构建可见网格的显式列表,那么无论何时您找到可见节点,都可以很容易地将整个可见子代网格范围合并到您的列表中

    EDIT2:我显著更新了代码。我添加了一个基于半预订单输入流的递归模型生成器。我修复了一些错误。最重要的是,
    Node *curr = model.getRoot();  // TODO: handle empty tree
    Node *next;
    bool  shouldnt_visit_children;
    
    while (1)
    {
      // TODO: visit curr -- determine shouldnt_visit_children
    
      if (shouldnt_visit_children || NULL == (next = model.getChild(curr)))
      {
        // move to next sibling; if no siblings remain then ascend while no siblings remain
    
        while (NULL == (next = model.getNextSibling(curr)))
          if (model.getEnd() == (curr = model.getParent(curr)))
            goto DONE;
    
        curr = next;
      }
      else
        curr = next;  // descend to first child
    }
    
    DONE:;
    
    1
            5
                    0
                    2
                            7
                                    0
                                    0
                                    0
                                    1
                                            0
                                    0
                                    0
                                    0
                            2
                                    1
                                            0
                                    0
                    1
                            0
                    4
                            1
                                    0
                            2
                                    1
                                            0
                                    1
                                            0
                            0
                            0
                    0
    
    john-schultzs-macbook-pro:~ jschultz$ clear; ./a.out < input.txt
    
    Node -1 is complete! mParentIndex = 0; mIndex = 0; mChildrenBegin = 1; mChildrenEnd = 2
    Node 0 is complete! mParentIndex = 0; mIndex = 1; mChildrenBegin = 2; mChildrenEnd = 7
    Node 1 is complete! mParentIndex = 1; mIndex = 2; mChildrenBegin = 7; mChildrenEnd = 7
    Node 2 is complete! mParentIndex = 1; mIndex = 3; mChildrenBegin = 7; mChildrenEnd = 9
    Node 6 is complete! mParentIndex = 3; mIndex = 7; mChildrenBegin = 9; mChildrenEnd = 16
    Node 8 is complete! mParentIndex = 7; mIndex = 9; mChildrenBegin = 16; mChildrenEnd = 16
    Node 9 is complete! mParentIndex = 7; mIndex = 10; mChildrenBegin = 16; mChildrenEnd = 16
    Node 10 is complete! mParentIndex = 7; mIndex = 11; mChildrenBegin = 16; mChildrenEnd = 16
    Node 11 is complete! mParentIndex = 7; mIndex = 12; mChildrenBegin = 16; mChildrenEnd = 17
    Node 15 is complete! mParentIndex = 12; mIndex = 16; mChildrenBegin = 17; mChildrenEnd = 17
    Node 12 is complete! mParentIndex = 7; mIndex = 13; mChildrenBegin = 17; mChildrenEnd = 17
    Node 13 is complete! mParentIndex = 7; mIndex = 14; mChildrenBegin = 17; mChildrenEnd = 17
    Node 14 is complete! mParentIndex = 7; mIndex = 15; mChildrenBegin = 17; mChildrenEnd = 17
    Node 7 is complete! mParentIndex = 3; mIndex = 8; mChildrenBegin = 17; mChildrenEnd = 19
    Node 16 is complete! mParentIndex = 8; mIndex = 17; mChildrenBegin = 19; mChildrenEnd = 20
    Node 18 is complete! mParentIndex = 17; mIndex = 19; mChildrenBegin = 20; mChildrenEnd = 20
    Node 17 is complete! mParentIndex = 8; mIndex = 18; mChildrenBegin = 20; mChildrenEnd = 20
    Node 3 is complete! mParentIndex = 1; mIndex = 4; mChildrenBegin = 20; mChildrenEnd = 21
    Node 19 is complete! mParentIndex = 4; mIndex = 20; mChildrenBegin = 21; mChildrenEnd = 21
    Node 4 is complete! mParentIndex = 1; mIndex = 5; mChildrenBegin = 21; mChildrenEnd = 25
    Node 20 is complete! mParentIndex = 5; mIndex = 21; mChildrenBegin = 25; mChildrenEnd = 26
    Node 24 is complete! mParentIndex = 21; mIndex = 25; mChildrenBegin = 26; mChildrenEnd = 26
    Node 21 is complete! mParentIndex = 5; mIndex = 22; mChildrenBegin = 26; mChildrenEnd = 28
    Node 25 is complete! mParentIndex = 22; mIndex = 26; mChildrenBegin = 28; mChildrenEnd = 29
    Node 27 is complete! mParentIndex = 26; mIndex = 28; mChildrenBegin = 29; mChildrenEnd = 29
    Node 26 is complete! mParentIndex = 22; mIndex = 27; mChildrenBegin = 29; mChildrenEnd = 30
    Node 28 is complete! mParentIndex = 27; mIndex = 29; mChildrenBegin = 30; mChildrenEnd = 30
    Node 22 is complete! mParentIndex = 5; mIndex = 23; mChildrenBegin = 30; mChildrenEnd = 30
    Node 23 is complete! mParentIndex = 5; mIndex = 24; mChildrenBegin = 30; mChildrenEnd = 30
    Node 5 is complete! mParentIndex = 1; mIndex = 6; mChildrenBegin = 30; mChildrenEnd = 30
    Beginning Semi-Pre-Order traversal of tree!
    Visiting 0
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 0
    Will visit children of 0
    Visiting 1
    Will visit sibling of 1
    Visiting 2
    Will visit sibling of 2
    Visiting 3
    Will visit sibling of 3
    Visiting 4
    Will visit sibling of 4
    Visiting 5
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 1
    No children to visit of 1
    Trying to visit children of 2
    Will visit children of 2
    Visiting 6
    Will visit sibling of 6
    Visiting 7
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 6
    Will visit children of 6
    Visiting 8
    Will visit sibling of 8
    Visiting 9
    Will visit sibling of 9
    Visiting 10
    Will visit sibling of 10
    Visiting 11
    Will visit sibling of 11
    Visiting 12
    Will visit sibling of 12
    Visiting 13
    Will visit sibling of 13
    Visiting 14
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 8
    No children to visit of 8
    Trying to visit children of 9
    No children to visit of 9
    Trying to visit children of 10
    No children to visit of 10
    Trying to visit children of 11
    Will visit children of 11
    Visiting 15
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 15
    No children to visit of 15
    Reached end of siblings again -> completed traversal of tree rooted by parent 11
    Moved back up to 11
    Found a great^Nth uncle (12) to check for children to visit!
    Trying to visit children of 12
    No children to visit of 12
    Trying to visit children of 13
    No children to visit of 13
    Trying to visit children of 14
    No children to visit of 14
    Reached end of siblings again -> completed traversal of tree rooted by parent 6
    Moved back up to 6
    Found a great^Nth uncle (7) to check for children to visit!
    Trying to visit children of 7
    Will visit children of 7
    Visiting 16
    Will visit sibling of 16
    Visiting 17
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 16
    Will visit children of 16
    Visiting 18
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 18
    No children to visit of 18
    Reached end of siblings again -> completed traversal of tree rooted by parent 16
    Moved back up to 16
    Found a great^Nth uncle (17) to check for children to visit!
    Trying to visit children of 17
    No children to visit of 17
    Reached end of siblings again -> completed traversal of tree rooted by parent 7
    Moved back up to 7
    Moved back up to 2
    Found a great^Nth uncle (3) to check for children to visit!
    Trying to visit children of 3
    Will visit children of 3
    Visiting 19
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 19
    No children to visit of 19
    Reached end of siblings again -> completed traversal of tree rooted by parent 3
    Moved back up to 3
    Found a great^Nth uncle (4) to check for children to visit!
    Trying to visit children of 4
    Will visit children of 4
    Visiting 20
    Will visit sibling of 20
    Visiting 21
    Will visit sibling of 21
    Visiting 22
    Will visit sibling of 22
    Visiting 23
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 20
    Will visit children of 20
    Visiting 24
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 24
    No children to visit of 24
    Reached end of siblings again -> completed traversal of tree rooted by parent 20
    Moved back up to 20
    Found a great^Nth uncle (21) to check for children to visit!
    Trying to visit children of 21
    Will visit children of 21
    Visiting 25
    Will visit sibling of 25
    Visiting 26
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 25
    Will visit children of 25
    Visiting 27
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 27
    No children to visit of 27
    Reached end of siblings again -> completed traversal of tree rooted by parent 25
    Moved back up to 25
    Found a great^Nth uncle (26) to check for children to visit!
    Trying to visit children of 26
    Will visit children of 26
    Visiting 28
    No more siblings to visit! Try to visit their children. Rewinding to oldest sibling 28
    No children to visit of 28
    Reached end of siblings again -> completed traversal of tree rooted by parent 26
    Moved back up to 26
    Moved back up to 21
    Found a great^Nth uncle (22) to check for children to visit!
    Trying to visit children of 22
    No children to visit of 22
    Trying to visit children of 23
    No children to visit of 23
    Reached end of siblings again -> completed traversal of tree rooted by parent 4
    Moved back up to 4
    Found a great^Nth uncle (5) to check for children to visit!
    Trying to visit children of 5
    No children to visit of 5
    Reached end of siblings again -> completed traversal of tree rooted by parent 0
    Moved back up to 0
    Finished Semi-Pre-Order traversal of tree!
    
        case AXIS_DESCENDANT:
        case AXIS_DESCENDANT_OR_SELF:
            switch(node_type)
            {
            case NODE_TYPE_NODE:
            case NODE_TYPE_ELEMENT:
                {
                    // as far as I know the type node is considered to be
                    // the same as elements (at least in XPath 1.0)
                    QDomNode node(context_node);
                    if(axis == AXIS_DESCENDANT_OR_SELF
                    && (local_part.isEmpty() || local_part == context_node.toElement().tagName())
                    && (any_prefix || prefix == context_node.prefix()))
                    {
                        result.push_back(context_node);
                    }
                    while(!node.isNull())
                    {
                        QDomNode next(node.firstChild());
                        if(next.isNull())
                        {
                            next = node;
                            while(!next.isNull()) // this should never happend since we should bump in context_node first
                            {
                                if(next == context_node)
                                {
                                    // break 2;
                                    goto axis_descendant_done;
                                }
                                QDomNode parent(next.parentNode());
                                next = next.nextSibling();
                                if(!next.isNull())
                                {
                                    break;
                                }
                                next = parent;
                            }
                        }
                        // the local_part & prefix must match for us to keep the node
                        node = next;
                        if((local_part.isEmpty() || local_part == node.toElement().tagName())
                        && (any_prefix || prefix == node.prefix()))
                        {
                            // push so nodes stay in document order
                            result.push_back(node);
                        }
                    }
    axis_descendant_done:;
                }
                break;
    
            default:
                throw QDomXPathException_NotImplemented(QString("this axis (%1) does not support this node type (%2)").arg(static_cast<int>(axis)).arg(static_cast<int>(node_type)).toStdString());
    
            }
            break;
    
    struct Node {
        ... node data ...
        int next;
    };
    
    std::vector<Node> nodes;
    
    void check(int ix) {
        switch(nodes[ix].visibility()) {
            case VISIBLE:
                // Draw whole subtree, no more checking needed
                for (int i=ix,e=nodes[ix].next; i!=e; i++) {
                    nodes[i].draw();
                }
                break;
            case PARTIALLY_VISIBLE:
                nodes[ix].draw();
                for (int c=ix+1, e=nodes[ix].next; c!=e; c=nodes[c].next) {
                    check(c);
                }
                break;
        }
    }