Каков самый быстрый способ десериализации дерева в C++

Я работаю с не такой уж маленькой древовидной структурой (это Burkhard-Keller-Tree, > 100 МБ памяти), реализованной на C++. Указатели на дочерние элементы каждого узла хранятся в 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 ответов


другим подходом было бы сериализовать ваши указатели и восстановить их при загрузке. Я имею в виду:

сериализация:

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, это может быть быстрее со списками или даже векторами).


прежде всего - профилируйте свое приложение так, чтобы вы знали, что требует времени-основывая подозрение на новом, потому что вы где-то читали, что оно может быть медленным или на итерации по дереву недостаточно.

возможно, это операции ввода-вывода - возможно, ваш формат файла неверен / неэффективен.

может быть, у вас просто где-то дефект?

или, может быть, где-то есть квадратичный цикл, который вы не помните о возникновении проблем? :)

измерьте, что действительно занимает время в вашем случае, а затем подойдите к проблеме - это сэкономит вам много времени, и вы не будете нарушать свой дизайн/код, чтобы исправить проблемы с производительностью, которые не существуют, прежде чем найти реальную причину.


рекомендую повысить библиотеку сериализации. Он должен работать с решениями, которые вы используете.


самый быстрый способ сериализации / десериализации-это запись блока непрерывной памяти на диск, как вы говорите. Если вы изменили древовидную структуру, чтобы создать это (возможно, используя пользовательскую процедуру распределения), это будет очень легко.

к сожалению, я не так хорошо знаком с QHash, но, глядя на него, он выглядит как хэш-таблица, а не дерево. Я вас неправильно понял? Вы используете это для сопоставления дубликатов узлов?

Я бы использовал профилировщик (я использовал Quantify, теперь называется Rational PurifyPlus, но есть много перечисленные здесь), чтобы найти, где вы используете время, но я бы предположил, что это либо несколько выделений памяти, а не одно выделение, или несколько чтений, а не одно чтение. Чтобы решить обе эти проблемы, вы заранее знаете (потому что вы храните его), сколько узлов вам нужно, затем напишите / прочитайте массив узлов правильной длины, где каждый указатель является индексом в массив, а не указателем в память.


другим решением было бы использовать собственный распределитель памяти, который будет использовать непрерывное пространство памяти. Затем вы сможете сбросить память, как и загрузить ее позже. Это платформа (т. е. big endian/little endian, 32bit/64bit) чувствительная.


Как вы сказали, выделение объектов с новым может быть медленным. Это можно улучшить, выделив пул объектов, а затем используя предварительно выделенные объекты, пока пул не будет исчерпан. Возможно, вы даже сможете реализовать это для работы в фоновом режиме, перегрузив операторы new/delete соответствующего класса.


ваше собственное распределение памяти с перегруженным оператором new() и delete () является недорогим вариантом (время разработки). Однако это влияет только на время выделения памяти, а не на время Ctor. Ваш пробег может отличаться, но может стоит попробовать.


Я немного расширю свой комментарий:

поскольку ваше профилирование предполагает, что сериализация QHash занимает больше всего времени, я считаю, что замена QHash на QList даст значительное улучшение, когда дело доходит до скорости десериализации.

сериализация QHash просто выводит пары ключ / значение, но десериализация создает структуру хэш-данных!

даже если вы сказали, что вам нужен быстрый поиск ребенка, я бы рекомендовал вам попробовать замена QHash на QList > в качестве теста. Если для каждого узла не так много детей (скажем, менее 30), поиск должен быть достаточно быстрым даже с QList. Если вы обнаружите, что QList недостаточно быстр, вы все равно можете использовать его только для (de)сериализации, а затем преобразовать в хэш после загрузки дерева.