2009-09-11 3 views
2

Я пишу дерево B + по целому ряду причин, и я пришел сюда, чтобы задать вопрос о реализации его узлов. Мои узлы в настоящее время выглядят следующим образом:B + tree implementation, * * vs *

struct BPlusNode 
{ 
public: 
    //holds the list of keys 
    keyType **keys; 
    //stores the number of slots used 
    size_t size; 
    //holds the array of pointers to lower nodes NULL if this is a leaf node 
    BPlusNode **children; 
    //holds the pointer to the next load to the 'left' 
    BPlusNode *next; 
    //Data page pointers NULL if this is a branch node 
    Bucket **pages; 
}; 

Как вы можете видеть, что моя текущая реализация использует * * в том месте, где мне интересно, должен ли я использовать * или *.

Мне хорошо известно, что * * требует двух операций разыменования и, следовательно, медленнее, чем просто использование *, однако этот класс использует большую рекурсию, и гораздо более удобно передавать указатели на подвыборы рекурсивных функций. Для этого с помощью * мне нужно будет выполнить арифметику указателя и передать полученный указатель.

С **

someFunction(BPlusNode* currNode) 
{ 
    ...... 
    someFunction(currNode->children[ChildIndex]); 
} 



с *

someFunction(BPlusNode* currNode) 
{ 
    ...... 
    someFunction((currNode->children) + ChildIndex); 
} 

Я могу видеть, что есть дополнительное чтение из памяти для получения указателя на нужный * * версии, но * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Есть ли у кого-нибудь мысли так или иначе? Предложения по третьему варианту? Доказательство того, почему человек превосходит другого? и т.д?

Edit:
Я мог бы отправить это как ответ ниже, но я просто понял, что с * * схемами не нужно скопировать все содержимое каждого подузла или ведро должен я хочу вставить одну в середину массив (т. е. изменить размер массива). Если для схемы * есть 20 поднодов, когда я перераспределяю массив, мне нужно будет копировать байты 20 * sizeof (BPlusNode), а не байты размером 20 * sizeof (BPlusNode *) для схемы * *.

С другой стороны, мне пришло в голову, что, поскольку я выполняю все вставки и разбиения страниц, возможно, это увеличивает эффективность при их исполнении, а преимущества * over * * в поисках перевешивают его.

+2

Как это отмечено на C++, есть ли какая-то причина, по которой вы не можете передавать указатели по ссылке вместо выполнения арифметики указателя? – greyfade

+0

Как я понимаю, он делает что-то вроде someFunction (BPlusNode * и currNode) .... и затем вызывает его через someFunction (currNode-> children [ChildIndex]), будет даже хуже, чем * *. [] В основном совпадает с * (currNode-> children + ChildIndex), таким образом, то же, что и схема * *, есть арифметика указателя, а затем разыменование. В отличие от схемы * *, этот указатель на объект должен быть извлечен и передан. Поэтому мне кажется, что с точки зрения эффективности он по крайней мере эквивалентен схеме * *. Может быть, хуже. –

+0

@James: Я уверен, что greyfade предлагает подпись 'someFunction (BPlusNode & currNode)'. Это функционально (и с точки зрения производительности), идентичное 'someFunction (BPlusNode * currNode)', но выглядит более чистым и позволяет избежать ошибок, которые могут быть вызваны случайным изменением указателя (вместо объекта pointee). –

ответ

2

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

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

Это может выглядеть примерно следующее:

enum BPlusNodeType { 
    LEAF, BRANCH 
}; 

struct BPlusNodeData { 
    static const size_t max_size = 511; // Try to fit into 4K? 8K? 
    uint16_t size; 
    uint16_t type; 
    keyType key[max_size]; 
    union { 
     Bucket* data[max_size]; 
     BPlusNodeData* children[max_size]; 
    }; 
}; 
+0

+1. Массив узловых указателей фиксированного размера будет быстрее и чище (без учета динамической памяти (de/re)), чем решение на основе '*' или '**'. –

+0

Мне нравится элегантность здесь, но я немного размыта по синтаксису. Я знаю, как профсоюз работает во втором контексте, но как первая декларация профсоюза вписывается в что-либо? –

+0

Кроме того, какое влияние будет на то, как вы это сделаете, если бы я сказал вам, что keyType - это большая уродливая вещь. К сожалению, keyType - это действительно то, что я называю численным массивом, это по существу динамический массив для хранения от 1 до n мерных координат, а затем преобразование этих координат в 1D-индекс с кривой заполнения гильбертовым пространством (с использованием некоторой побитовой магии). Это влияет на то, как вы это сделаете? –

1

Используя **, вам понадобится дополнительный шаг выделения для удержания каждого указателя BPlusNode*. Или вы можете выделить блок из них и просто указать каждый указатель в children на последовательные BPlusNode* элементы внутри этого блока - но это еще одно дополнительное распределение динамической памяти на создание узла (и соответствующий дополнительный шаг удаления на уничтожение). Поэтому я бы абсолютно рекомендовал использовать один *. Если писать

someFunction((currNode->children) + ChildIndex); 

болит, вы можете записать его в виде

someFunction(&currNode->children[ChildIndex]); 

который я нахожу более ясным.

+0

Но не делает ли это так же, как выполнение арифметики указателя, заменяя результат арифметики, а затем извлекая этот указатель узла? Кроме того, мне нужно всего лишь вставлять в дерево все в одном спешке, ведь в текущем наборе данных будет около 2,557 миллиардов вызовов вставки (хотя из-за структуры страниц всего около 255 700 кодов). Так как дерево только порядка 256, в этой начальной спешке будет много расщепления, так что, не будем ли мы пользоваться большей легкостью изменения размеров массивов? –

+0

Я не уверен, что вы имеете в виду - два фрагмента кода, которые я опубликовал, функционально * идентичны * во всех отношениях, и точно такой же код будет создан для них. «&» Во втором фрагменте предотвращает разыменование указателя - происходит только арифметика указателя, чтобы получить адрес в качестве конечного результата. (И вы понимаете, что ваша '**' версия также выполняет одну и ту же арифметику указателя, правильно? И затем делает дополнительную разметку указателя.) Кроме того, ни на одном этапе не присутствует какой-либо указатель 'this' здесь, я смущен тем, что вы значит там. –

+0

Ну, я неправильно понял, что происходит с & (что-то [index]). Я думал, что он выполнил разыменование, тогда ему пришлось вернуться, чтобы найти указатель на структуру. А как насчет эффекта во время вставок? Не нужно перераспределять и копировать каждый узел в массиве, который необходимо изменить? –

0

Вы бы лучше использовать STL «vector<keyType *> keys» и «vector<BPlusNode *> children», и так далее?

Это может быть слишком упрощенным, но мое впечатление заключается в том, что двойная косвенность не часто необходима в C++ (и не все, что часто на C, хотя чаще, чем на C++).

+0

Использование STL добавляет замедляет работу еще больше. Эта программа должна обрабатывать огромное количество чтений из индекса (и еще более массивное число с диска). Я хочу, чтобы запросы к этому дереву были как можно быстрее. –

+0

Наконец, usd std :: vector и т. Д. Делает узлы более крупными. У Vector есть некоторые связанные с ним пространственные накладные расходы, и хотя это немного меньше, когда у меня только пара миллиардов событий и, следовательно, пара сотен тысяч страниц и всего тысяча узлов или около того, это не так плохо (несмотря на то, что keyType принимает по существу, отдельная комната). Но когда вы смотрите на ситуацию, когда есть несколько сотен миллиардов или, что еще хуже, несколько триллионов событий (суммы, которые иногда необходимы), то можно обнаружить, что как можно меньше накладных расходов памяти. –

+0

@James: Чтение/запись 'vector ' будет не медленнее, чем чтение/запись динамически выделенного массива 'keyType *' - любой достойный компилятор будет генерировать идентичный код для обоих. OTOH «векторные» служебные данные являются реальными (хотя я сомневаюсь в значительном). Обычно я защищал бы замену любого массива с фиксированным или динамическим распределением на «вектор», но в этом случае я думаю, что целесообразно использовать подход с фиксированным размером Zan Lynx, который будет как меньше, так и быстрее. –