LINUX.ORG.RU

ЧТО Я (ОНО) ТАКОЕ?

 , , , ,


0

1

Привет ребята.

Пришло ко мне сегодня желание навелосипедить по полной.

Навелосипедил:

struct node_s {

    void          *value;
    struct node_s *children[256];

};


typedef struct node_s node_t;


node_t *
create_node()
{
    return calloc(1, sizeof(node_t));
}


node_t *
create_storage()
{
    return create_node();
}


int
insert(node_t *root, char *key, void *value)
{
    node_t *curr_node = root;

    size_t k;
    size_t c;

    for (k = 0; key[k]; k++) {
        c = (size_t) key[k];

        if (NULL == curr_node->children[c]) {
            curr_node->children[c] = create_node();
        }

        curr_node = curr_node->children[c];
    }

    if (NULL != curr_node->value) {
        return 0;
    }

    curr_node->value = value;

    return 1;
}

void *
get(node_t *root, char *key)
{
    node_t *curr_node = root;

    size_t k;
    size_t c;

    for (k = 0; key[k]; k++) {
        c = (size_t) key[k];

        if (NULL == curr_node->children[c]) {
            return NULL;
        }

        curr_node = curr_node->children[c];
    }

    return curr_node->value;
}

...
...
...


node_t *storage = create_storage();

// тут вставили 1113 тестовых элементов
// по строковым ключам разной длины (от 1 до 39 символов),
// каждый из них уникален,
// но большая часть не уникальна по префиксам
insert(storage, "blahblah", "A_LA_POINTER");

// типа ищем

clock_t tic = clock();

size_t i;

for (i = 0; i < 500000; i++) {
    get(storage, "blahblah_not_found_key");
}

clock_t toc = clock();

printf("test time : %f\n", (double) (toc - tic) / CLOCKS_PER_SEC);
// ~0.000207
И ещё я посчитал сколько места такая неэффективная страхолюдина занимает для всех этих индексов: 1697228 байт.

Ну, это, конечно, швах. Да хрен с ним. Дело не в этом.

А вот в этом:

Мне нужна какая-то, внешне вот стаким интерфейсом хреновина, контейнер для данных.

Я посмотрел на хеш таблицу, но:

1) Хеш таблица, перед тем как искать, в цикле по строке получает хеш сумму строкового ключа, и только потом ищет по заявленному O(1). У меня же тут сразу во время цикла по строке происходит поиск. Хеширование ключа не требуется.

2) Доступная мне реализация хеш таблицы «искаропки» требует кучу каких-то танцеваний при инициализации. Так ещё и просит указать хотябы примерное количество элементов, которые в ней будут храниться. А я не знаю!!! Поставишь много — пожрёт много. Поставишь мало — в какой-то момент изза перестройки при выделении дополнительного куска памяти для индексов просядет скорость. Опять же у меня в велосипеде, кажется, такой проблемы нет — скорость (наверное) всегда примерно средняя, каллочит почуть и ладно.

Ну, размер выделенной памяти, конечно, ужас. 1.6 метра на 1.1к строчек. Или не ужас?

Нет, не хочу использовать свой велосипед. Он просто получился.

А вы, господа гуру, подскажите-ка немощному страдальщику какой мне контейнер правильно выбрать под свои хотелки?

правильно выбрать под свои хотелки?

сначала хотелки надо правильно сформулировать

Harald ★★★★★
()
Ответ на: комментарий от Harald

сформулировать

Ня:

// по строковым ключам разной длины (от 1 до 39 символов), // каждый из них уникален, // но большая часть не уникальна по префиксам

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от fsb4000

Потому что плюсовики тоже используют контейнеры. А зачем ты сюда зашел? Лучше контейнер подходящий посоветуй.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

В хотелках не уточняется, что для тебя важнее, компактность хранения, скорость поиска, скорость запихивания

А так trie тут уже посоветовали

Harald ★★★★★
()
Ответ на: комментарий от Harald

Самое важное — скорость. Именно поэтому там в велосипеде, если ты посмотришь внимательно, по сути, полный игнор того, сколько памяти на это надо. Хотя при большом кол-ве ключей, которые не уникальны префиксами — память не будет так расти. Но это велосипед же.

deep-purple ★★★★★
() автор топика
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от deep-purple

но большая часть не уникальна по префиксам

В плюсах можно было бы попробовать обычный std::map, но с comparator-ом, который сравнивает ключи в обратном порядке. Если различаются именно суффиксы, то такое сравнение обрывается быстрее на разных ключах, чем если сравнивать значения от начала к концу.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

обычный std::map

Который вроде как — бинарное дерево.

Да, переворачивать строки я тоже хотел. Всмысле — сравнивать с конца.

deep-purple ★★★★★
() автор топика
Последнее исправление: deep-purple (всего исправлений: 2)
Ответ на: комментарий от deep-purple

У тебя статически на ноду 256 детей сделано. ИМХО память вылетает в трубу в том числе и на этом. Проитерировать на небольшом количестве элементов в поисках нужного будет дешевле на современных процессорах чем раздувать размер структуры настолько, что она не влезает в кэш.

timdorohin ★★★★
()
Ответ на: комментарий от timdorohin

память вылетает в трубу

Но скорость важнее.

Проитерировать на небольшом количестве элементов

А как же скорость?

раздувать размер структуры настолько, что она не влезает в кэш

А вот тут хорошо подмечено. Есть касяк.

deep-purple ★★★★★
() автор топика

Ну, походу префиксное дерево — то что надо.

Спасибо всем!

Но я это я ещё погоняю, потестирую.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Если элементы сортированы, то там бинпоиском находится относительно быстро, ну и за счет меньшего размера оно в кэш влезает и этим ускоряется значительно.

timdorohin ★★★★
()
Ответ на: комментарий от timdorohin

Так погоди — в велосипеде и сортировать не нужно.

А чтобы уменьшить размеры памяти — тут надо индекс потомков мутить, к какому чару потомок есть и в какой позиции он там сидит (типа сколько раз сделать children->next).

Но не кажется ли тебе, что поиск сразу, без предварительной подготовки чего-либо, пусть и по большой памяти, и пусть она из кеша выгрузилась — вгрузить обратно хардварными средствами проца будет быстрее, чем вот эти все танцевания в велосипеде или не в велосипеде?

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Тестить надо на конкретной задаче.

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

timdorohin ★★★★
()
Ответ на: комментарий от timdorohin

Ну дык. Я и написал что буду тестить и сравнивать.

А то что на один узел в велосипеде надо 1028 байт (и это я щас на 32 битной) — это конечно жостко.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

префиксное дерево

Если заполнение дерева отдельно от поиска, то можно сжать префиксное дерево

anonymous
()

Если C++, то можно попробовать std::unordered_map

zx_90
()

ЧТО Я (ОНО) ТАКОЕ?

Я тоже себе этот вопрос задаю :(

Владимир

anonymous
()

Начнем с простого, тебе нужно сделать массив контейнеров на 39 (39 - 1) элементов. n-й контейнер хранит только ключи длины n. Это – самый важный момент. В качестве контейнеров можешь взять как map, так и trie, это не так важно.

С trie будет избыточность деревьев, их придется сжимать. Я бы посоветовал unordered_map, но это зависит от числа элементов. Пока их 1113, точно стоит использовать unordered_map.

Siborgium ★★★★★
()

PATRICIA, оно же path compressed trie, оно же critbit tree.

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

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

Говорят, хорошие результаты показывают B-деревья, но в данном случае, ИМХО плацебо.

Поэтому, компромиссный вариант. Если ключей не слишком много, то рассчитать 64-битный хеш хорошей современной функцией. Хранить в дереве по вкусу, игнорируя коллизии. Можно балансировкой вообще не заморачиваться (оно будет примерно сбалансированным), можно хранить в AVL или красно-чёрном дереве (реализацию взять из ядра линукса если лицензия не важна, или из ядра *BSD), можно использовать AMT/HAMT.

Но право слово, я бы не заморачивался. std::mapstd::string — и ваши волосы будут мягкими и шелковистыми. А потом уже профилировать, буде потребуется (не потребуется, уверяю).

anonymous
()

Хеш таблица, перед тем как искать, в цикле по строке получает хеш сумму строкового ключа, и только потом ищет по заявленному O(1). У меня же тут сразу во время цикла по строке происходит поиск

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

Lrrr ★★★★★
()
Ответ на: комментарий от anonymous

судя по задаваемым вопросам

Я лишь спросил какой контейнер тут подойдет.

тебе нужно ещё многому учиться

Конечно, только дурак скажет что знает всё.

прежде чем ты сможешь приступить к реализации

Должны пройти выходные, а в рабочие дни приступлю.

Под каждую задачу реализуют что-то свое

Вот и мне нужно что-то своё, подходящее. Чтобы поиск был молниеносным, а вставка можно и не очень, удаление вообще предполагается только при завершении процесса.

я бы не заморачивался. std::map

Эээ, неее, тут сишечка.

Если ключей не слишком много

Это сейчас их не слишком много.

хранить в дереве по вкусу, игнорируя коллизии

Ты в своём уме коллизии игнорировать? Сказано, уникальные ключи, значит уникальные.

рассчитать 64-битный хеш

Следи за руками:

При использовании хеш таблицы, для любого действия (вставка, поиск), при получении строкового ключа на вход, эта входная строка хешируется КАЖДЫЙ РАЗ. Её нужно пройти в цикле и примененить к каждому символу сдвиги, ксоры и прочее. И лишь только после этих действий, «поиск» по полученному хешу происходит за O(1).

В таком случае у моего велосипеда вообще O(0), т.к. прямо во время цикла по строке уже ищется значение.

Можно балансировкой вообще не заморачиваться

Да, в велосипеде не нужны ни балансировки, ни сортировки, нет проблем с коллизиями — там всё сразу работает как надо, хоть обвставляйся и обудаляйся. Смущает лишь одно — требующаяся память...

судя по твоему коду

А что мой код? Я потратил пол часа на его придумывание и написание. Его ты посмотрел настолько бегло, что у тебя даже родилась мысль о моей тупости. Смотри, сам таковым не прослыви.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от Lrrr

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

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

Ээээ, нет, стоп, давай не будем уходить в обсуждение велосипеда. Скажу только: он лучше остальных во всём, кроме использования памяти.

deep-purple ★★★★★
() автор топика
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от deep-purple

При использовании хеш таблицы, для любого действия (вставка, поиск), при получении строкового ключа на вход, эта входная строка хешируется КАЖДЫЙ РАЗ. Её нужно пройти в цикле и примененить к каждому символу сдвиги, ксоры и прочее. И лишь только после этих действий, «поиск» по полученному хешу происходит за O(1).

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Только хеш, замерял конечно — да, быстро. Однако, хеш таблица требует заранее определить сколько я буду хранить в ней элементов. А я не знаю сколько. Конечно, она может и динамически перестроиться, но вот это перестроение может начаться в очень неподходящий момент. А у меня там ивент драйвен — нельзя долго держать одеяло на ком-то одном. Надо по быстрому что-то сделать и отпустить.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

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

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от deep-purple

Тогда дерево. Смотря какие данные, как распределены.

Можно было бы дополнительно схитрить: для ключей короче некоторой длины, у которых не так много возможных значений (я бы взял длину в 4-8 байт, смотря какая версия SSE доступна) вообще не заниматься никакими хешами, а хранить в массиве как есть, та самая unordered_map и получится. Должно будет хорошо вписаться в кеш. Для ключей длиннее этого значения строить префиксное дерево глубиной в разницу длины и этого значения, в листьях хранить все те же массивы с ключами без префикса.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)

Мне нужна какая-то, внешне вот стаким интерфейсом хреновина, контейнер для данных.

Вот.

https://github.com/orangeduck/Corange/blob/master/include/data/dict.h

https://github.com/orangeduck/Corange/blob/master/src/data/dict.c

Важно Размер dict должен быть больше раза в два количества элементов в нём. В ином случае выбирай или быстро или экономно.

anonymous
()
Ответ на: комментарий от deep-purple

А я не знаю сколько

Как только количество элементов будет занимать 70% увеличивай размер в два раза и перестраивай. Это должно происходить автоматически при добавления нового элемента и да при этом иногда будут разовые просадки по скорости из за этой перестройки, но часто на это плевать ибо раз в хренпериодный отрезок времени происходит

anonymous
()

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

anonymous
()
Ответ на: комментарий от anonymous

но часто на это плевать

Мне не плевать. Сколько вставок, столько и «писателей». Читателей же, тех, которые потом ищут по ключу нужный ресурс, в среднем в 100 раз больше. И когда какой-то «писатель» запустит своей вставкой перестроение — будет швах. Нет, я ещё не мерял как оно вживую просядет, т.к. ещё не выбран контейнер. В любом случае я обернусь в единый интерфейс, а какой из контейнеров там «под капотом» будет — для пользующихся не важно. Ну и повтыкаю разные контейнеры и посмотрю на результаты тестов.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от anonymous

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

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Сколько вставок, столько и «писателей».

Ааааааааа.

Ну и повтыкаю разные контейнеры и посмотрю на результаты тестов.

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

anonymous
()
Ответ на: комментарий от deep-purple

что там будет очередной стандартный контейнер

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

anonymous
()
Ответ на: комментарий от anonymous

А вот хрен его знает

А зачем советуете, если не знаете?

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

вообще не заниматься никакими хешами, а хранить в массиве как есть

И делать strncmp? Так это тоже цикл. Или превращать короткие строки в числа? А смысл? Это будет та же самая хештабля.

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

И тут мы вернулись к моему велосипеду, который в точности это и делает, только не откидывая префиксы.

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Или превращать короткие строки в числа? А смысл? Это будет та же самая хештабля.

Превращать в числа и искать по этим числам, как по ключам. Искать, а не осуществлять прямой доступ. Простой линейный поиск по интам в малом массиве, отлично векторизуется.

Это будет та же самая хештабля.

…для которой не нужны будут «ксоры» и в целом какой-либо расчет хеша. Вы просто берете (unsigned int*)(&c), откусывая по sizeof (unsigned int) от строки и пользуетесь этим. Можете, кстати, и префикс кусками нарезать, а не побайтово.

только не откидывая префиксы

Вот именно.

Для начала, благодаря исходному разбиению по длинам ключей, у вас будет O(1) доступ к контейнеру, содержащему искомый ключ, что уже сужает круг поиска в 39 (на самом деле нет, при равномерном распределении длинных ключей больше, чем коротких) раз. За счет этого каждый отдельно взятый контейнер будет содержать значительно меньше элементов, чем если бы все ключи лежали в одном контейнере, и все эти элементы будут одной длины. В данный момент на каждый лист дерева вы затрачиваете 256 * 8 байт лишней памяти. Если вы будете точно знать, что данная нода – лист, вы сможете обойтись без этих затрат. Если вы будете знать обратное, вы сможете обойтись без хранения *value в ноде. Уже этим мое решение улучшает ваш результат.

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

На выходных набросаю код, если вам интересно.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 5)
Ответ на: комментарий от Siborgium

Да я иду примерно тем же путём.

На выходных набросаю код, если вам интересно

Мне интересно. Но выходные — это именно что выходные. А у меня есть интересы личного характера — вот DLNA ковыряю. И хотел бы потратить время именно на них, не отвлекаясь и не забивая голову тем, о чём буду думать в рабочее время. Чего и вам советую. Ну нафик!!!

deep-purple ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Ну и пральна. Тут как в естественном отборе побеждает самый приспособленный алгоритм.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.