LINUX.ORG.RU

История изменений

Исправление firkax, (текущая версия) :

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

Если «очередь» состоит чисто из целых чисел, которые надо расставить по дереву в правильном порядке - то да, выглядит красиво, хотя и так, по-моему, желательно увеличить количество потомков у узла (например 4), чтобы делать меньше перестановок.

Если же очередь состоит из некоторых структур с другими данными (и в том числе эти структуры могут участвовать в каких-то других списках, то есть перемещать их на другое место в памяти это не просто memcpy) - то всё получается сложнее. Тут получается два с половиной варианта:

1) Хранить в куче сами структуры. При операциях над кучей тасовать их будет приходиться затратно, как минимум таскать сопутствующие кроме ключа сравнения данные, как максимум - каждый раз корректировать какие-нить ссылки на них из других мест где они нужны.

2) Хранить в куче указатели на структуры, а сами структуры делать malloc или хранить в ещё каком-нить массиве (в этом случае вместо указателей можно индексы, они меньше места займут) где они будут на фиксированном месте. Проблемы первого варианта исчезают, появляются другие: для сравнения двух узлов придётся лезть по указателям куда-то далеко (хотя для малых применений это не критично), а ещё в структурах надо хранить их текущие индексы в куче, и корректировать их при перестановках - опять лазить по указателям. Причём все эти операции гарантированно требуются в количестве примерно log2(N) при извлечении вершины.

2а) Хранить в куче указатели на структуры + рядом закешированные ключи сравнения, чтобы хотя бы сравнивать без указателей, ценой удвоения расхода памяти на кучу, хотя наверно это несущественно т.к. структуры данных всё равно занимают заметно больше.

Во вариантах 1 и 2а, если увеличить количество потомков у узла, можно заметно уменьшить описанные вредные эффекты.

Во варианте 2а, с хранением структур в индексированном статическом массиве, можно сделать например так: 4 байта на индекс, 4 байта на ключ сравнения, 8 потомков у узла, итого описание всех его потомков займёт 64 байта - размер кеш-линии проца (и надо выровнять чтобы оно на правильные адреса попадало), что наверно положительно скажется на скорости. При этом сравнение ключей будет без указателей, а в указатели лезть только для свапа одного из 8 потомков (в 3 раза реже чем с двоичной кучей).

Ну и всё-таки, сравнение с обычным b-tree (дерево с блоками размера процовой страницы т.е. 4кб): в нём указанных проблем с лишними хождениями по указателям почти нет, постоянно делать свапы не нужно, иногда приходится делать сплит или объединение блоков но это иногда - редко, и затрагиваются этой операцией обычно только 1-2 узла. И можно без проблем хранить структуры в самом btree без лишних указателей. Куча точно лучше?

Исходная версия firkax, :

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

Если «очередь» состоит чисто из целых чисел, которые надо расставить по дереву в правильном порядке - то да, выглядит красиво, хотя и так, по-моему, желательно увеличить количество потомков у узла (например 4), чтобы делать меньше перестановок.

Если же очередь состоит из некоторых структур с другими данными (и в том числе эти структуры могут участвовать в каких-то других списках, то есть перемещать их на другое место в памяти это не просто memcpy) - то всё получается сложнее. Тут получается два с половиной варианта:

1) Хранить в куче сами структуры. При операциях над кучей тасовать их будет приходиться затратно, как минимум таскать сопутствующие кроме ключа сравнения данные, как максимум - каждый раз корректировать какие-нить ссылки на них из других мест где они нужны.

2) Хранить в куче указатели на структуры, а сами структуры делать malloc или хранить в ещё каком-нить массиве (в этом случае вместо указателей можно индексы, они меньше места займут) где они будут на фиксированном месте. Проблемы первого варианта исчезают, появляются другие: для сравнения двух узлов придётся лезть по указателям куда-то далеко (хотя для малых применений это не критично), а ещё в структурах надо хранить их текущие индексы в куче, и корректировать их при перестановках - опять лазить по указателям. Причём все эти операции гарантированно требуются в количестве примерно log2(N) при извлечении вершины.

2а) Хранить в куче указатели на структуры + рядом закешированные ключи сравнения, чтобы хотя бы сравнивать без указателей, ценой удвоения расхода памяти на кучу, хотя наверно это несущественно т.к. структуры данных всё равно занимают заметно больше.

Во вариантах 1 и 2а, если увеличить количество потомков у узла, можно заметно уменьшить описанные вредные эффекты.

Во варианте 2а, с хранением структур в индексированном статическом массиве, можно сделать например так: 4 байта на индекс, 4 байта на ключ сравнения, 8 потомков у узла, итого описание всех его потомков займёт 64 байта - размер кеш-линии проца (и надо выровнять чтобы оно на правильные адреса попадало), что наверно положительно скажется на скорости. При этом сравнение ключей будет без указателей, а в указатели лезть только для свапа одного из 8 потомков (в 3 раза реже чем с двоичной кучей).

Ну и всё-таки, сравнение с обычным b-tree: в нём указанных проблем с лишними хождениями по указателям почти нет, постоянно делать свапы не нужно, иногда приходится делать сплит или объединение блоков но это иногда - редко, и затрагиваются этой операцией обычно только 1-2 узла. И можно без проблем хранить структуры в самом btree без лишних указателей. Куча точно лучше?