“逻辑上更慢”的算法结果更快,但为什么呢?

时间:2013-11-21 14:16:16

标签: c++ performance algorithm tree visibility

我已经实现了两种基本相同的不同算法,检查节点树中从一个节点到另一个节点的可见性,规则很简单 - 只有节点在其前面的节点之前才可见同一个分支。

第一种方法将树从子节点下移到父节点,跳过父节点中的其他潜在子节点以获取两个节点的树索引,并使用一些基本逻辑来确定是否存在可见性。我决定首先选择这个,因为我已经拥有了我需要的节点索引的方法,我认为它可能更快。

bool isVisibleTo(Node * accessor) {
  QList<uint> accessedI = getIndex();
  QList<uint> accessorI = accessor->getIndex();
  if (accessedI.size() > accessorI.size()) {
    return false; 
  } else if (accessedI.size() == accessorI.size()) { 
    for (int i = 0; i < accessedI.size() - 1; ++i) {
      if (accessedI.at(i) != accessorI.at(i)) {
        return false; 
      }
    }
    if (accessedI.last() > accessorI.last()) {
      return false; 
    }
  }
  for (int i = 0; i < accessorI.size() - (accessorI.size() - accessedI.size()); ++i) {
    if (accessedI.at(i) > accessorI.at(i)) {
      return false; 
    }
  }
  return true;
}

第二个完全遍历树,每个孩子一直遍历父节点,依此类推,经历了更多节点,我只能假设内存页面和缓存行。

bool isVisibleTo2(Node * accessor) {
  Node * node = accessor;
  while (node) {
    if (node == this)
      return true;
    if (node->_parent) {
      uint i = node->_parent->_children.indexOf(node);
      while (i) {
        if (node->_parent->_children.at(--i) == this) {
          return true;
        }
      }
    }
    node = node->_parent;
  }
  return false;
}

我预计这将成为大树的较慢算法。但事实证明小树的速度提高了10-20倍,并且随着树木尺寸的增加,它在最后几次测试中保持了4倍的稳定性,最后的测试耗时约20分钟,并且在树中有1000万个节点(授予大部分时间是节点分配,实际可见性检查是几秒钟。

那些性能数据是由什么原因造成的?考虑到它们提供相同的结果(彻底检查 - 第二种方法没有保存工作),第一种方法涉及更少的内存跃点,我认为它更加缓存友好,而且它只能检查深度并做得更短评价?当然,它会进行2次遍历,而不是一次,但是它们直接与父级相关,在此过程中会跳过其余的孩子。是的,我确实意识到第二种方法不需要一直向下,但仍然......

编辑:我切换到-O3编译,但数字没有改变。我还试图将getIndex的列表更改为向量,但它实际上导致了大量的性能下降,因为索引需要以相反的顺序插入,例如,前置,对于向量来说效率非常低。

编辑2:再次使用矢量进行了快速测试,这次我先废弃前置并在返回前进行​​常规插入和反向操作,这使得矢量解决方案稍微快一些,比完全遍历慢8倍方法“仅”慢6倍。我怀疑QList分配可能是低性能的主要罪魁祸首,但看起来还有更多的东西。

4 个答案:

答案 0 :(得分:1)

如果我理解正确的话getIndex()函数,你在第一种情况下调用的函数基本上与isVisibleTo2()所做的所有树相同。但isVisibleTo1()还有getIndex操作,因此速度较慢。

答案 1 :(得分:0)

第一种方法有一个很大的缺点,它似乎每次调用时都会为最多2n个节点分配空间(在QLists中),这可能会破坏你的缓存并占用你的堆。 / p>

我更关心的问题:你是否100%确定这些功能是否相同?我知道你已经运行了许多节点的测试,但确实检查以确保函数为各种树返回相同的答案。

答案 2 :(得分:0)

有关实际结构方法的更多信息可能会改变这一点,但是:

这里另一个可能的区别是分支可预测性

似乎第二个版本可能比第一个版本更可预测

答案 3 :(得分:0)

这很容易。第一个版本的问题是你正在调用两次getIndex(),其中分配内存。您可以通过发布getIndex()的代码来证明它或反驳它。

顺便说一下,没有什么“逻辑上更慢”没有调用昂贵的功能。