我已经实现了两种基本相同的不同算法,检查节点树中从一个节点到另一个节点的可见性,规则很简单 - 只有节点在其前面的节点之前才可见同一个分支。
第一种方法将树从子节点下移到父节点,跳过父节点中的其他潜在子节点以获取两个节点的树索引,并使用一些基本逻辑来确定是否存在可见性。我决定首先选择这个,因为我已经拥有了我需要的节点索引的方法,我认为它可能更快。
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
分配可能是低性能的主要罪魁祸首,但看起来还有更多的东西。
答案 0 :(得分:1)
如果我理解正确的话getIndex()
函数,你在第一种情况下调用的函数基本上与isVisibleTo2()
所做的所有树相同。但isVisibleTo1()
还有getIndex操作,因此速度较慢。
答案 1 :(得分:0)
第一种方法有一个很大的缺点,它似乎每次调用时都会为最多2n个节点分配空间(在QLists
中),这可能会破坏你的缓存并占用你的堆。 / p>
我更关心的问题:你是否100%确定这些功能是否相同?我知道你已经运行了许多节点的测试,但确实检查以确保函数为各种树返回相同的答案。
答案 2 :(得分:0)
有关实际结构方法的更多信息可能会改变这一点,但是:
这里另一个可能的区别是分支可预测性
似乎第二个版本可能比第一个版本更可预测
答案 3 :(得分:0)
这很容易。第一个版本的问题是你正在调用两次getIndex(),其中分配内存。您可以通过发布getIndex()的代码来证明它或反驳它。
顺便说一下,没有什么“逻辑上更慢”没有调用昂贵的功能。