在C ++中反序列化树的最快方法是什么

时间:2009-12-16 10:35:10

标签: c++ performance qt serialization tree

我正在使用C ++实现的一个不那么小的树结构(它是一个Burkhard-Keller-Tree,在内存中大于100 MB)。指向每个节点的子节点的指针存储在QHash中。

每个节点x有n个子节点y [1] ... y [n],子节点的边缘用编辑距离d(x,y [i])标记,因此使用散列来存储节点是一个明显的解决方案。

class Node {
    int value;
    QHash<int, Node*> children;
    /* ... */
};

我还想将它序列化并反序列化为一个文件(我目前使用的是QDataStream)。树只构建一次,然后不会改变。

构建树并对其进行反序列化非常缓慢。我以明显的方式加载树:递归构建每个节点。我认为这是次优的,因为许多节点是与new运算符分开创建的。我在某处读到new非常慢。初始构建不是一个大问题,因为树相当稳定,不必经常重建。但是从文件加载树应该尽可能快。

实现这一目标的最佳方法是什么?

将整个树保存在具有相邻节点的单个内存块中一定要好得多。然后将序列化和反序列化减少以保存和加载整个块,我必须只分配一次。

但要实现这一点,我必须重新实施QHash,AFAIK。

你会采取什么措施来加速反序列化?

附录

感谢您建议进行一些分析。结果如下:

从文件重建树时

 1 % of the time is consumed by my own new calls
65 % is consumed by loading the QHash objects (this is implemented by the 
     Qt Library) of each node
12 % is consumed by inserting the nodes into the existing tree
20 % is everything else

所以这绝对不是我的新调用导致延迟,而是在每个节点重建QHash对象。这基本上完成了:

 QDataStream in(&infile);
 in >> node.hash;

我是否需要深入了解QHash,看看幕后发生了什么?我认为最好的解决方案是一个哈希对象,可以使用单个读写操作进行序列化,而无需重建内部数据结构。

8 个答案:

答案 0 :(得分:4)

首先 - 描述你的应用程序,以便你知道什么需要时间 - 基于新的怀疑,因为你读过某个地方它可能很慢或者通过树的迭代是不够的。

这可能是IO操作 - 也许你的文件格式不正确/效率低。

也许你只是在某处有缺陷?

或许某个地方有一个二次循环,你不记得导致这些问题? :)

衡量你的案例真正需要花费时间,然后解决问题 - 它会节省你很多时间,你会避免破坏你的设计/代码来修复在找不到真正原因之前不存在的性能问题

答案 1 :(得分:3)

另一种方法是序列化指针并在加载时恢复它们。我的意思是:

<强>序列化

nodeList = collectAllNodes();

for n in nodelist:
 write ( &n )
 writeNode( n ) //with pointers as-they-are.

<强>反序列化

//read all nodes into a list.
while ( ! eof(f))
    read( prevNodeAddress)
    readNode( node )
    fixMap[prevNodeAddress] = &node;
    nodeList.append(node);

//fix pointers to new values.
for n in nodeList:
    for child in n.children:
        child->node = fixMap[child->node]

这样,如果你不插入 - 删除新节点,你可以分配一次向量并使用那个内存,减少你对地图的分配(正如RPG所说,列表甚至向量可能更快)。

答案 2 :(得分:1)

我强烈推荐boost serialization library。它应该与你正在使用的解决方案一起使用。

答案 3 :(得分:1)

序列化/反序列化的绝对最快方法是将一块连续内存写入磁盘,如你所说。如果您更改了树结构以创建它(可能使用自定义分配例程),这将非常容易。

不幸的是,我对QHash并不熟悉,但从它看起来它看起来像一个Hashtable而不是一棵树。我误会了你吗?您是否使用它来映射重复的节点?

我会使用一个分析器(我以前使用Quantify,现在称为Rational PurifyPlus,但有很多listed here)来查找你在哪里使用时间,但我猜它是多个内存分配而不是单个分配,或多次读取而不是单个读取。要解决这两个问题你事先知道(因为你存储它们)你需要多少个节点,然后写/读一个正确长度的节点数组,其中每个指针是数组的索引,而不是内存中的指针

答案 4 :(得分:0)

另一种解决方案是使用自己的内存分配器,它将使用连续的内存空间。然后你就可以按原样转储内存并加载回来。它的平台(即大端/小端,32位/ 64位)敏感。

答案 5 :(得分:0)

正如您所说,使用new分配对象可能会很慢。这可以改进分配对象池,然后使用预先分配的对象,直到池耗尽为止。您甚至可以通过重载相关类的new / delete运算符来实现此功能。

答案 6 :(得分:0)

使用重载运算符new()和delete()进行自己的内存分配是一种低成本选项(开发时间)。 但是,这只会影响内存分配时间,而不会影响Ctor时间。 您的里程可能会有所不同,但值得一试。

答案 7 :(得分:0)

我会稍微扩展我的评论:

由于您的分析表明QHash序列化占用的时间最多,我相信用QList替换QHash会在反序列化速度方面产生显着的改进。

QHash序列化只输出键/值对,但反序列化构造了一个哈希数据结构!

即使你说你需要快速查找孩子,我建议你尝试用QList替换QHash&gt;作为测试。如果每个节点没有多个子节点(例如,小于30),即使使用QList,查找仍应足够快。如果你发现QList不够快,你仍然可以将它用于(de)serializaton,然后在加载树后转换为哈希。