Высокоэффективный map<some_string_type,some_datum>
От: cranky Украина  
Дата: 02.09.05 17:56
Оценка: 65 (3) -1
В последнее время на РСДН участились обсуждения отображений с ключами-строками:
Re[19]: Про итераторы
Автор: jedi
Дата: 27.08.05

Быстрое сравнение строк
Автор: Fahrain
Дата: 27.07.05

и другие...

В большинстве реализаций STL существуют два подхода для организации отображений: map и hash_map. Ни тот, ни другой не являются эффективными для не-скалярных типов, таких как массивы, строки и т.п.
map использует red-black-tree со сравнением целых строк в каждом узле.
hash_map считает для целой строки хеш-код, затем сравнивает строки целиком, причём кол-во таких сравнений равно кол-ву строк с совпадающим хеш-кодом. Дополнительный недостаток — значения при добавлении не сортируются по ключам.
Но есть и третий подход — использование деревьев цифрового поиска (trie). Такая структура данных позволяет находить нужный узел на основании внутреннего представления строки, сравнивая строки лишь один раз. Наиболее эффективной из таких структур является PATRICIA trie, изобретенная в 1968 Дональдом Р. Моррисоном специально для быстрого поиска в больших объемах текстовой информации. Возможности такого дерева весьма высоки, краткое описание алгоритма добавления/поиска в нем можно увидеть, например, в 3-ем томе "Искусства программирования" Кнута.

Исходя из всего этого, у меня возникла идея создать шаблон контейнера pat_map, который бы в обобщённом виде реализовал структуру PATRICIA trie, и был бы однообразным и совместимым с другими контейнерами и алгоритмами STL.
Заголовочный файл с шаблоном и пример использования (concordance). (компилируется MSVC 6, Intel C++ 6, MSVC 7.1)
Для тестирования производительности собрал два эквивалентных релиза (MSVC 7.1 with max optimization): этот
Автор: jedi
Дата: 27.08.05
(с использованием std::map) и свой (using pat_map).
Времена подсчёта словоупотреблений для файла 72М (очищенные русские тексты, слова в нижнем регистре и пробел) на P4-2.8 1G RAM:
Результаты вычислений совпадают. Максимум использования RAM для обеих программ был одинаков и составил 20М.
Как видно, производительность в случае использования правильной структуры данных повысилась на 25%.
Вообще говоря, это ещё не окончательная версия (в которой планируются pat_set + удаление узлов + некоторые оптимизации + полная возможная совместимость с STL), но даже в таком виде шаблон pat_map будет полезен.

PS Пожелания и предложения принимаются с благодарностью
You aren't expected to absorb this
trie_map & trie_set, v0.17
От: R.K. Украина  
Дата: 05.10.05 09:42
Оценка: 22 (2)
Здравствуйте, _nn_, Вы писали:

__>Здравствуйте, R.K., Вы писали:


RK>>Насчёт дальнейшего развития: это ещё не всё, но имеются некоторые алгоритмические трудности. Будут идеи, свободное время — будут новые версии.


__>Ждем


Дождались
trie_map &amp; trie_set, v0.17 (MSVC 7.1, GCC 3.2, Comeau 4.3):

Немного о планах (в приблизительном порядке невозрастания реализуемости;):

__>Было бы неплохо сделать более полный тест скорости работы класса.


Это да... Вот только с чем сравнивать будем?
You aren't expected to absorb this
trie_map & trie_set, v0.11
От: R.K. Украина  
Дата: 21.09.05 16:02
Оценка: 8 (2)
Привет всем, кого ещё интересует судьба моих контейнеров

trie_map &amp; trie_set, v0.11 (MSVC 7.1)
Первая версия, в которой всё появившееся ранее работает и при этом не вызывает отвращения.
Подробнее:
Вроде ничего не забыл.


Кратко о предыдущих версиях:

v0.07

v0.05
You aren't expected to absorb this
Re: trie_map & trie_set, v0.11
От: R.K. Украина  
Дата: 26.09.05 07:04
Оценка: 5 (1)
Привет всем!

trie_map &amp; trie_set, v0.13 (MSVC 7.1, GCC 3.2, Comeau 4.3)
Ничего особо нового, только теперь компилируется с вышеперечисленными компиляторами (указаны минимальные номера версий, доступных для проверки). Comeau C++ тестировался онлайн. Кстати, только он в режиме strict выдал варнинг на отсутствие return *this; в _Myt &operator=(const _Myt&) в trie_generic. Остальные компилеры предательски молчали, MSVC в /W4, а GCC, соответственно, в -pedantic -Wextra.
Удивительно ещё то, что GCC собрал релиз, выполняющийся быстрее на треть (32%), в сравнении с MSVC с max speed optimizations.
Насчёт дальнейшего развития: это ещё не всё, но имеются некоторые алгоритмические трудности. Будут идеи, свободное время — будут новые версии.
You aren't expected to absorb this
Re[2]: trie_(map|set), v0.19 и тесты
От: R.K. Украина  
Дата: 12.10.05 05:39
Оценка: 5 (1)
Здравствуйте, _nn_, Вы писали:

__>Здравствуйте, R.K., Вы писали:


RK>>
RK>>MSVC 7.1 (w/o PAT_SHORTCUTS) File: 93 М (russian words)

RK>>*** pat::trie_set
RK>>Timer: 37.3867 sec.
RK>>Size: 404463
RK>>Timer erase: 0.297726 sec.
RK>>*** std::set
RK>>Timer: 54.5871 sec.
RK>>Size: 404463
RK>>Timer erase: 0.302945 sec.
RK>>*** std::hash_set
RK>>Timer: 28.5497 sec.
RK>>Size: 404463
RK>>*** std::hash_set: to std::vector + std::sort
RK>>Timer: 1.42755 sec.
RK>>Size: 404463
RK>>Full hash timer: 29.9773 sec.


RK>>GCC 3.4.4 (w/o PAT_SHORTCUTS) File: 93 М (russian words)

RK>>*** pat::trie_set
RK>>Timer: 27.9667 sec.
RK>>Size: 404463
RK>>Timer erase: 0.476102 sec.
RK>>*** std::set
RK>>Timer: 45.1105 sec.
RK>>Size: 404463
RK>>Timer erase: 0.482932 sec.
RK>>*** std::hash_set
RK>>Timer: 16.078 sec.
RK>>Size: 404463
RK>>*** std::hash_set: to std::vector + std::sort
RK>>Timer: 1.94486 sec.
RK>>Size: 404463
RK>>Full hash timer: 18.0229 sec.
RK>>
__>


__>Что-то я недопонял тест, hash_set получается выходит намного быстрее чем pat::trie_set ?


Да. Но hash_set предполагается сравнивать с trie_set<DelusionString>:
MSVC 7.1 (PAT_SHORTCUTS) File: 93 М (russian words)

*** pat::trie_set
Timer: 34.4867 sec.
Size: 404463
*** pat::trie_set<pat::DelusionString>
Timer: 29.6441 sec.
Size: 404463


GCC 3.4.4 (PAT_SHORTCUTS) File: 93 М (russian words)

*** pat::trie_set
Timer: 25.2501 sec.
Size: 404463
*** pat::trie_set<pat::DelusionString>
Timer: 23.0039 sec.
Size: 404463

Ибо именно DelusionString с соответствующим бит-компаратором (и с включённым PAT_SHORTCUTS) эмулирует работу hash_table. Естественно, в результате получается множество, сортированное по хеш-значениям ключей, т.е. хорошо перемешанное, зато временные показатели добавления будут близки к хешированию, что мы и наблюдаем в тестах Для GCC это менее заметно, видимо реализация hash_set там лучше, чем в VC.
Вот только возможности trie-контейнеров по различным способам перебора элементов намного превышают стандартные, а уж тем более hash-контейнеры, это можно увидеть в предыдущих моих сообщениях.
Кстати, забыл одну вещь: в версии 0.19 появилась возможность хранить элементы в обратном порядке ключей назначением контейнеру pat::reverse_bit_comparator (что-то вроде std::greater вместо std::less), например:
pat::trie_map<std::string,unsigned,pat::reverse_bit_comparator<std::string> >
You aren't expected to absorb this
Re: Высокоэффективный map<some_string_type,some_datum>
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.09.05 22:40
Оценка:
Здравствуйте, cranky, Вы писали:

Теперь возми хорошую реализацию хэш-таблицы и хэшфункции и убедись, что потратил время зря.
... << RSDN@Home 1.2.0 alpha rev. 606>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Высокоэффективный map<some_string_type,some_datum>
От: cranky Украина  
Дата: 04.09.05 11:56
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, cranky, Вы писали:


VD>Теперь возми хорошую реализацию хэш-таблицы и хэшфункции и убедись, что потратил время зря.


Взял std::hash_map из MSVC 7.1 и хеш-функцию:
class string_hash_compare
    : public hash_compare<string>
{
public:
    size_t operator()(const string &key) const
    {
        size_t h=0;
        for (string::const_iterator it=key.begin();it!=key.end();++it)
            h=5*h+*it;
        return h;
    }
    bool operator()(const string &key1,const string &key2) const
    {
        return comp(key1,key2);
    }
};

Достаточно хороши?
Получил на том же тексте 24.1 сек. и хорошо перемешанный список слов с их частотой. Или может есть хеш-функция, позволяющая хранить записи, сортированные по ключу?
На всякий случай напомню: это ещё только зачаточная версия контейнера. В нём планируются такие возможности, которые хешированию так же недоступны, как и хранение в отсортированном виде. В частности, PATRICIA trie позволяет использовать в качестве ключей бесконечные строки. Это может быть полезно для нахождения всех позиций в большом файле, где начинается искомая строка.
You aren't expected to absorb this
Re[3]: Высокоэффективный map<some_string_type,some_datum>
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.09.05 15:41
Оценка:
Здравствуйте, cranky, Вы писали:

C>Достаточно хороши?

C>Получил на том же тексте 24.1 сек.

Я бы назвал эту реализацию посредственной. Но как видишь она уже показала себя лучше дерева.

C> и хорошо перемешанный список слов с их частотой. Или может есть хеш-функция, позволяющая хранить записи, сортированные по ключу?


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

C>На всякий случай напомню: это ещё только зачаточная версия контейнера. В нём планируются такие возможности, которые хешированию так же недоступны, как и хранение в отсортированном виде. В частности, PATRICIA trie позволяет использовать в качестве ключей бесконечные строки. Это может быть полезно для нахождения всех позиций в большом файле, где начинается искомая строка.


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

В большинстве реализаций STL существуют два подхода для организации отображений: map и hash_map. Ни тот, ни другой не являются эффективными для не-скалярных типов, таких как массивы, строки и т.п.

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

Нумаю ты понимашь, что чем сложнее тип используемый в качестве ключа, тем эффективнее может быть хэш-таблица? Ведь хэш-значение можно вычислить один раз и закэшировать в кэземпляре. Далее поиск слота осуществляется практически в костантное время и при минимуме вычислений. А сравнение ведется на очень малом объеме списка. В итоге чем больше объем данных — тем фээективнее хэш-таблица. Для защиты от коллизий можно вместо списка коллизий хранить деревья или отсортированные массивы значений. В общем, алгоритмически у чистых деревьев просто нет шансов. Смысл в них есть только если нужна постоянная отсортированность. Что является довольно большой редкостью.
... << RSDN@Home 1.2.0 alpha rev. 606>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Высокоэффективный map<some_string_type,some_datum>
От: cranky Украина  
Дата: 04.09.05 17:54
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Я не спорю, что твой контейнер имеет право на существование и хорош в некоторых случаях. Я не согласен с твоим утверждением:

VD>

В большинстве реализаций STL существуют два подхода для организации отображений: map и hash_map. Ни тот, ни другой не являются эффективными для не-скалярных типов, таких как массивы, строки и т.п.

VD>Сам по себе hash_map — это классическая таблица. То что он не важно реализован в некоторых версиях STL еще ни о чем не говорит. К тому же hash_map не входит в стандарт и является частным расширением. Ну, а его опрадванность ты мог наблюдать сам.

Оки, я согласен, что hash_map я зацепил по инерции, но тогда он просто не из той оперы, ибо добивается несколько иных целей и делает это, естественно, по-другому. Хотя... Дело в том, что положение узла в trie определяется исходя из значений битов, указывающих, в какую сторону поворачивать. Но никто не ограничивает, в каком порядке эти биты располагаются в ключе, и есть ли они там вообще. Следовательно, возможно придумать такой порядок вычисления битов, который будет эквивалентен значению хеш-функции от этого ключа. Получается, что на деревьях можно эмулировать работу сколь угодно сложной хеш-таблицы, естественно также без гарантии сортированности по ключу. Тем более в моей реализации выдача значений битов ключа вынесена в отдельный класс.

VD>Нумаю ты понимашь, что чем сложнее тип используемый в качестве ключа, тем эффективнее может быть хэш-таблица? Ведь хэш-значение можно вычислить один раз и закэшировать в кэземпляре. Далее поиск слота осуществляется практически в костантное время и при минимуме вычислений.


Аналогично и с порядком выборки битов: если его сложно вычислять — он кешируется.

VD>А сравнение ведется на очень малом объеме списка. В итоге чем больше объем данных — тем фээективнее хэш-таблица. Для защиты от коллизий можно вместо списка коллизий хранить деревья или отсортированные массивы значений. В общем, алгоритмически у чистых деревьев просто нет шансов.


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

VD>Смысл в них есть только если нужна постоянная отсортированность. Что является довольно большой редкостью.


Отсортированность — необязательное условие для деревьев цифрового поиска. Ею при необходимости можно пожертвовать.
You aren't expected to absorb this
Re[5]: Высокоэффективный map<some_string_type,some_datum>
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.09.05 21:04
Оценка:
Здравствуйте, cranky, Вы писали:

C>Оки, я согласен, что hash_map я зацепил по инерции, но тогда он просто не из той оперы, ибо добивается несколько иных целей и делает это, естественно, по-другому.


Именно. Причем в обычно задач для которых достаточно функциональности хэштаблицы намного больше. В дотнете любой объект обязательно реализует функцию хэширования. Это как раз сделано для того чтобы максимально упростить использование хэш-таблиц.

C> Хотя... Дело в том, что положение узла в trie определяется исходя из значений битов, указывающих, в какую сторону поворачивать. Но никто не ограничивает, в каком порядке эти биты располагаются в ключе, и есть ли они там вообще. Следовательно, возможно придумать такой порядок вычисления битов, который будет эквивалентен значению хеш-функции от этого ключа. Получается, что на деревьях можно эмулировать работу сколь угодно сложной хеш-таблицы, естественно также без гарантии сортированности по ключу. Тем более в моей реализации выдача значений битов ключа вынесена в отдельный класс.


Что то я тебя не пойму. Причем тут биты? Дериво и так польностью эмулирвет поведение хэш-таблицы. Только делает оно это на баже поддержания сортированности. А это, с одной стороны требует того чтобы элементы коллекции можно было сравнивать на болше/меньше, что не всегда удобно/допустимо (например, что больше Иванов или Сидоров?). А сдругой стороны — это банально более медненно. Ведь для поиска элементов в бинарном дереве поиска используется бинарный поиск (полвинным делением), а в хэш-таблице просто вычисляется хэш-функция и на ее основании вычисляется слот хэш-таблицы. Если коллезий нет, то скорость просто константная.

C>Аналогично и с порядком выборки битов: если его сложно вычислять — он кешируется.


О каких битах ты все время ведешь речь? Чтобы найти элемент в дереве ты обязан сравнить ключ с корневой веткой, если ключ больше начать поиск в левом поддереве, если меньше, в правом. В нем ты снова обязан полностью соравинть ключь с ключем из ветки и так далее пока не найдется нужны ключ или не обнаружится, что такового нет. Заранее сравнение вычислить нельзя. В случае же с хэш-значением его можно формировать при создании объекта, а потом пользоваться гтовым значением.

C>Если смущает поиск по дереву — можно применить специальный вектор, содержащий указатели на вершины, соответствущие префиксу ключа заранее определённой длины. Тогда останется пройтись по дереву меньшего объёма — аналогия с деревом коллизий. Эта возможность тоже в планах.


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

C>Отсортированность — необязательное условие для деревьев цифрового поиска. Ею при необходимости можно пожертвовать.


Ох, ох, ох... Как же это не обязательное? Оно даже называется "Бинарное дерево поиска".
В общем, почитай вот эти вещи:
http://www.optim.ru/cs/2000/4/bintree_htm/beentree_main.asp
http://www.optim.ru/cs/2000/4/bintree_htm/hash.asp
А то у тебя какие-то странные представления. Или изложи свои идеи. Как же можно искать в неупорядоченных деревьях.
... << RSDN@Home 1.2.0 alpha rev. 606>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Высокоэффективный map<some_string_type,some_datum>
От: CreatorCray  
Дата: 04.09.05 22:49
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Что то я тебя не пойму. Причем тут биты?

VD>О каких битах ты все время ведешь речь?
VD>А то у тебя какие-то странные представления. Или изложи свои идеи. Как же можно искать в неупорядоченных деревьях.
Почитай теорию по Patricia деревьям, а...
А то похоже ты принцип их работы совершенно не понимаешь. Patricia дерево выглядит примерно так

0
0
1
0
-A-
0 1
0 0
1 0
B C
0 1 0 1
0 E 1 0
1 1 G
0 F
D

A — G — ноды (цифры над ними по дереву до самого верха — соответствующие им ключи)
т.е. для ноды F ключ = 0010100011, для ноды D = 00100010010
Но при этом можно создать такое дерево

0
0
1
0
---A
0 1
0 0
1
_B
0 1
0 0

1 1
0
С

Тут уже A = 001010, B = 0010001101, C = 00100010010

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

Пример: ключ поиска = 0010001101
Нода А — совпало 0010. Бит разницы — 0, перехожу на левого чайлда и сравниваю дальше
Нода В — совпало 001. Бит разницы — 1, перехожу на правого чайлда и сравниваю дальше
Нода В — совпало 101. Больше чайлдов нет — нашел нужную ноду

Пример: ключ поиска = 00101101
Нода А — совпало 0010. Бит разницы — 1, перехожу на правого чайлда и сравниваю дальше
Нода А — совпало 1. До бита разницы не дошло — искомой ноды нет
Re[7]: Высокоэффективный map<some_string_type,some_datum>
От: cranky Украина  
Дата: 05.09.05 05:46
Оценка:
Здравствуйте, CreatorCray, Вы писали:

[...]

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


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

CC>В результате поиск элемента в таком дереве линейна и зависит от длинны ключа.


Скорее сложность поиска это сумма [O(ln N) + O(K)], где N — общее кол-во узлов в дереве, K — длина ключа.
You aren't expected to absorb this
Re[7]: Высокоэффективный map<some_string_type,some_datum>
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.09.05 20:01
Оценка:
Здравствуйте, CreatorCray, Вы писали:

CC>В результате поиск элемента в таком дереве линейна и зависит от длинны ключа.


И зачем это нужно? Или ты не верно выразился, или это бесполезная трата времени.
... << RSDN@Home 1.2.0 alpha rev. 606>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Высокоэффективный map<some_string_type,some_datum>
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.09.05 20:01
Оценка:
Здравствуйте, cranky, Вы писали:

C>Скорее сложность поиска это сумма [O(ln N) + O(K)], где N — общее кол-во узлов в дереве, K — длина ключа.


Здорово. Скоростная характеристика хэш-тиблицы в отсуствии коллизий O(1) и не завист от размера ключа.
... << RSDN@Home 1.2.0 alpha rev. 606>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: Высокоэффективный map<some_string_type,some_datum>
От: cranky Украина  
Дата: 06.09.05 04:19
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, cranky, Вы писали:


C>>Скорее сложность поиска это сумма [O(ln N) + O(K)], где N — общее кол-во узлов в дереве, K — длина ключа.


VD>Здорово. Скоростная характеристика хэш-тиблицы в отсуствии коллизий O(1) и не завист от размера ключа.


А сложность вычисления хорошего хеш-значения ключа тоже стала константной и не зависит от длины ключа?
You aren't expected to absorb this
Re[10]: Высокоэффективный map<some_string_type,some_datum>
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.09.05 19:53
Оценка:
Здравствуйте, cranky, Вы писали:

C>А сложность вычисления хорошего хеш-значения ключа тоже стала константной и не зависит от длины ключа?


Зависит от алгоритма. Может и не зависить. Но если бы ты читал внимательнее, то заметил бы, что хэш легко кешируется. Так что вычислять его каждый раз не нужно.
... << RSDN@Home 1.2.0 alpha rev. 606>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Высокоэффективный map<some_string_type,some_datum>
От: c-smile Канада http://terrainformatica.com
Дата: 07.09.05 06:37
Оценка:
Здравствуйте, cranky, Вы писали:

C>PS Пожелания и предложения принимаются с благодарностью


Угу, вот еще мое:
http://rsdn.ru/Forum/Message.aspx?mid=824201&amp;only=1
Автор: c-smile
Дата: 25.09.04

На больших наборах TST лучше плюс всякие вкусности типа "найти похожий".
Re[2]: Высокоэффективный map<some_string_type,some_datum>
От: kluxer Украина  
Дата: 07.09.05 09:58
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Здравствуйте, cranky, Вы писали:


C>>PS Пожелания и предложения принимаются с благодарностью


CS>Угу, вот еще мое:

CS>http://rsdn.ru/Forum/Message.aspx?mid=824201&amp;only=1
Автор: c-smile
Дата: 25.09.04


Интересно, спасибо. Надо будет и себе такое сделать, думаю получится. Даже мнится, что перебор в partial_match будут делать итераторы, может даже стандартные

CS>На больших наборах TST лучше плюс всякие вкусности типа "найти похожий".


Хм, а если применить правильный аллокатор для узлов, возможно они и сравняются в производительности... Алгоритмы-то родственные, насколько я не напутал. Сложность одинакова. Вкусности и у моих шаблонов планируются, но потом
You aren't expected to absorb this
Оптимальная хеш-функция?
От: kluxer Украина  
Дата: 07.09.05 11:16
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, cranky, Вы писали:


C>>Достаточно хороши?

C>>Получил на том же тексте 24.1 сек.

VD>Я бы назвал эту реализацию посредственной. Но как видишь она уже показала себя лучше дерева.


Такой вопрос: какая хеш-функция будет оптимальной для реализации хеш-таблицы в STL VC 7.1?
Интересуюсь, потому что в "Компиляторах" приведена такая:
    size_t operator()(const string &key) const
    {
        size_t h=0,g;
        for (string::const_iterator it=key.begin();it!=key.end();++it)
        {
            if (g=(h=(h<<4)+*it)&0xF0000000)
                h^=g>>24^g;
        }
        return h;
    }

Называется hashpjw и в тестировании даёт "неплохие результаты для всех размеров таблиц". "Неплохие" в этой цитате — это наиболее быстрые? Но проведённые мною эксперименты с замером производительности дают странный результат — hash_map с hashpjw всегда немного отстаёт от моего мапа, в то время как "посредственная реализация" всегда несколько опережает его. Почему так получается и какая функция будет самой эффективной для моих данных (большое кол-во (300-500К) повторяющихся с разной частотой строк длиной 1..25 символов)?
You aren't expected to absorb this
Re: Оптимальная хеш-функция?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.09.05 12:36
Оценка:
Здравствуйте, kluxer, Вы писали:

K>Такой вопрос: какая хеш-функция будет оптимальной для реализации хеш-таблицы в STL VC 7.1?


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

K>Интересуюсь, потому что в "Компиляторах" приведена такая:

K>
    size_t operator()(const string &key) const
K>    {
K>        size_t h=0,g;
K>        for (string::const_iterator it=key.begin();it!=key.end();++it)
K>        {
K>            if (g=(h=(h<<4)+*it)&0xF0000000)
K>                h^=g>>24^g;
K>        }
K>        return h;
K>    }
K>

K>Называется hashpjw и в тестировании даёт "неплохие результаты для всех размеров таблиц".

Это как я понимаю переделанный под С++ вариант из "Красного дракона". Я не проводил личных эксперементов, но и так понятно, что данная хэш-функция рассчитана на короткие идентификаторы обычно встречаемые в компиляторах.

Насколько мне извесно в дотнете используется очень неплохая хэш-ункция для строк. Ее можно выковырять из Ротора (SSCLI). В дотнетом форуме даже как-то был флэйм. Один орел не мог поверить, что хэш-функции не могут давать уникальные значения, а его тесты давали уникальные результаты.

K> "Неплохие" в этой цитате — это наиболее быстрые? Но проведённые мною эксперименты с замером производительности дают странный результат — hash_map с hashpjw всегда немного отстаёт от моего мапа, в то время как "посредственная реализация" всегда несколько опережает его. Почему так получается и какая функция будет самой эффективной для моих данных (большое кол-во (300-500К) повторяющихся с разной частотой строк длиной 1..25 символов)?


Возможно вычисление хэш-функции оказывается на тестируемых объемах более затратным чем сам поиск. А возможно что функция на твоих данных дает много коллизий. Это легко определить опытным путем. Если проблема в скорости хэш-функции, то можно просто кэшировать результат. Если в качестве, то нужно ее выбрасывать.
... << RSDN@Home 1.2.0 alpha rev. 606>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Высокоэффективный map<some_string_type,some_datum>
От: kluxer Украина  
Дата: 07.09.05 17:10
Оценка:
C>Исходя из всего этого, у меня возникла идея создать шаблон контейнера pat_map, который бы в обобщённом виде реализовал структуру PATRICIA trie, и был бы однообразным и совместимым с другими контейнерами и алгоритмами STL.
C>Заголовочный файл с шаблоном и пример использования (concordance). (компилируется MSVC 6, Intel C++ 6, MSVC 7.1)

Версия 0.05 (компилируется MSVC 7.1)

Изменения к лучшему:

Отличия от set и map: некоторые различия в работе методов lower_bound и подобных. Обусловлены отсутствием предиката и присутствием бит-компаратора, а также возможностью бесконечного ключа. Но об этом в будущей документации.
You aren't expected to absorb this
Re[11]: Высокоэффективный map<some_string_type,some_datum>
От: CreatorCray  
Дата: 07.09.05 17:12
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, cranky, Вы писали:


C>>А сложность вычисления хорошего хеш-значения ключа тоже стала константной и не зависит от длины ключа?


VD>Зависит от алгоритма. Может и не зависить. Но если бы ты читал внимательнее, то заметил бы, что хэш легко кешируется. Так что вычислять его каждый раз не нужно.


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

Давайте этот спор решим просто: набросаем тестовую программку с выполнением пары-тройки приближенных к реальным задачам алгоритмов с применением hash_map и patricia trie (+можно еще и ternary tree до кучи)
И можно на практике посмотреть на эффективность.
Потому как к сырой теории у меня полного доверия нет. Сколько раз напарывался что в теории все гладко а на практике полное г.
Re[12]: Высокоэффективный map<some_string_type,some_datum>
От: CreatorCray  
Дата: 07.09.05 21:03
Оценка:
... дополнение к моей мессаге
набросал сравнивалку. Но только без hash_map — я щас в отпуске а дома только VC6 со стандартным STL
В общем случае ternary в 3-5 раз быстрее patricia. Тестил на русских текстах и на телефонном справочнике (список ФИО).
Но при этом в ternary пробежаться по списку строк никак нельзя — надо их самому хранить отдельно. Что увеличивает требования к памяти.

947494 уникальных слов

вставка слов из файла
Patricia: 14.368569 sec. 8788 ticks per call 110.5Mb
Ternary: 4.221286 sec. 2597 ticks per call 68.4Mb (чистое дерево, сами строки не хранятся)

повторное чтение файла и поиск слов в map
Patricia: 17.433085 sec. 10662 ticks per call
Ternary: 2.179576 sec. 1341 ticks per call
trie_map & trie_set, v0.07
От: R.K. Украина  
Дата: 12.09.05 21:13
Оценка:
Привет всем!

trie_map &amp; trie_set, v0.07 (MSVC 7.1)
Изменения:


v0.05
Изменения к лучшему:
Отличия от set и map: некоторые различия в работе методов lower_bound и подобных. Обусловлены отсутствием предиката и присутствием бит-компаратора, а также возможностью бесконечного ключа. Но об этом в будущей документации.
You aren't expected to absorb this
Re: trie_map & trie_set, v0.07
От: R.K. Украина  
Дата: 13.09.05 04:59
Оценка:
Здравствуйте, R.K., Вы писали:

RK>
  • введёны новые типы итераторов — partial match iterators (partimap ) — const_partimap, partimap.

    Вот что значит ночью посты писать! Конечно, не partimap, а partimator.
  • You aren't expected to absorb this
    Re[2]: trie_map & trie_set, v0.11
    От: _nn_ www.nemerleweb.com
    Дата: 01.10.05 08:17
    Оценка:
    Здравствуйте, R.K., Вы писали:

    RK>Привет всем!


    RK>trie_map &amp; trie_set, v0.13 (MSVC 7.1, GCC 3.2, Comeau 4.3)

    RK>Ничего особо нового, только теперь компилируется с вышеперечисленными компиляторами (указаны минимальные номера версий, доступных для проверки). Comeau C++ тестировался онлайн. Кстати, только он в режиме strict выдал варнинг на отсутствие return *this; в _Myt &operator=(const _Myt&) в trie_generic. Остальные компилеры предательски молчали, MSVC в /W4, а GCC, соответственно, в -pedantic -Wextra.
    RK>Удивительно ещё то, что GCC собрал релиз, выполняющийся быстрее на треть (32%), в сравнении с MSVC с max speed optimizations.
    RK>Насчёт дальнейшего развития: это ещё не всё, но имеются некоторые алгоритмические трудности. Будут идеи, свободное время — будут новые версии.

    Ждем
    Было бы неплохо сделать более полный тест скорости работы класса.
    http://rsdn.nemerleweb.com
    http://nemerleweb.com
    Re: trie_map & trie_set, v0.17
    От: _nn_ www.nemerleweb.com
    Дата: 06.10.05 16:38
    Оценка:
    Здравствуйте, R.K., Вы писали:

    __>>Было бы неплохо сделать более полный тест скорости работы класса.


    RK>Это да... Вот только с чем сравнивать будем?


    Между компиляторами и с std::map/std::set
    http://rsdn.nemerleweb.com
    http://nemerleweb.com
    trie_(map|set), v0.19 и тесты
    От: R.K. Украина  
    Дата: 10.10.05 12:47
    Оценка:
    Привет всем!

    trie_(map|set), v0.19 (MSVC 7.1, GCC 3.2, Comeau 4.3):

    Здравствуйте, _nn_, Вы писали:

    __>Здравствуйте, R.K., Вы писали:


    __>>>Было бы неплохо сделать более полный тест скорости работы класса.


    RK>>Это да... Вот только с чем сравнивать будем?


    __>Между компиляторами и с std::map/std::set


    ОК. Ловите результаты для P4-3.0, 1G RAM (исходники см. speed_test.cpp и speed_test_pq.cpp):

    MSVC 7.1 (PAT_SHORTCUTS) File: 93 М (russian words)
    
    *** pat::trie_set
    Timer: 34.4867 sec.
    Size: 404463
    *** pat::trie_set<pat::DelusionString>
    Timer: 29.6441 sec.
    Size: 404463
    
    
    MSVC 7.1 (w/o PAT_SHORTCUTS) File: 93 М (russian words)
    
    *** pat::trie_set
    Timer: 37.3867 sec.
    Size: 404463
    Timer erase: 0.297726 sec.
    *** std::set
    Timer: 54.5871 sec.
    Size: 404463
    Timer erase: 0.302945 sec.
    *** std::hash_set
    Timer: 28.5497 sec.
    Size: 404463
    *** std::hash_set: to std::vector + std::sort
    Timer: 1.42755 sec.
    Size: 404463
    Full hash timer: 29.9773 sec.
    
    
    GCC 3.4.4 (PAT_SHORTCUTS) File: 93 М (russian words)
    
    *** pat::trie_set
    Timer: 25.2501 sec.
    Size: 404463
    *** pat::trie_set<pat::DelusionString>
    Timer: 23.0039 sec.
    Size: 404463
    
    
    GCC 3.4.4 (w/o PAT_SHORTCUTS) File: 93 М (russian words)
    
    *** pat::trie_set
    Timer: 27.9667 sec.
    Size: 404463
    Timer erase: 0.476102 sec.
    *** std::set
    Timer: 45.1105 sec.
    Size: 404463
    Timer erase: 0.482932 sec.
    *** std::hash_set
    Timer: 16.078 sec.
    Size: 404463
    *** std::hash_set: to std::vector + std::sort
    Timer: 1.94486 sec.
    Size: 404463
    Full hash timer: 18.0229 sec.
    
    
    
    ---------------- [unique_]priority_queue
    
    
    MSVC 7.1 File: 93 М (russian words)
    
    *** std::priority_queue
    Timer: 66.5425 sec.
    *** pat::unique_priority_queue
    Timer: 31.2714 sec.
    *** pat::unique_priority_queue<std::set>
    Timer: 43.163 sec.
    
    
    GCC 3.4.4 File: 93 М (russian words)
    
    *** std::priority_queue
    Timer: 88.7093 sec.
    *** pat::unique_priority_queue
    Timer: 31.46 sec.
    *** pat::unique_priority_queue<std::set>
    Timer: 42.5287 sec.
    You aren't expected to absorb this
    Re: trie_(map|set), v0.19 и тесты
    От: _nn_ www.nemerleweb.com
    Дата: 11.10.05 20:05
    Оценка:
    Здравствуйте, R.K., Вы писали:

    RK>
    RK>MSVC 7.1 (w/o PAT_SHORTCUTS) File: 93 М (russian words)
    
    RK>*** pat::trie_set
    RK>Timer: 37.3867 sec.
    RK>Size: 404463
    RK>Timer erase: 0.297726 sec.
    RK>*** std::set
    RK>Timer: 54.5871 sec.
    RK>Size: 404463
    RK>Timer erase: 0.302945 sec.
    RK>*** std::hash_set
    RK>Timer: 28.5497 sec.
    RK>Size: 404463
    RK>*** std::hash_set: to std::vector + std::sort
    RK>Timer: 1.42755 sec.
    RK>Size: 404463
    RK>Full hash timer: 29.9773 sec.
    
    
    RK>GCC 3.4.4 (w/o PAT_SHORTCUTS) File: 93 М (russian words)
    
    RK>*** pat::trie_set
    RK>Timer: 27.9667 sec.
    RK>Size: 404463
    RK>Timer erase: 0.476102 sec.
    RK>*** std::set
    RK>Timer: 45.1105 sec.
    RK>Size: 404463
    RK>Timer erase: 0.482932 sec.
    RK>*** std::hash_set
    RK>Timer: 16.078 sec.
    RK>Size: 404463
    RK>*** std::hash_set: to std::vector + std::sort
    RK>Timer: 1.94486 sec.
    RK>Size: 404463
    RK>Full hash timer: 18.0229 sec.
    RK>


    Что-то я недопонял тест, hash_set получается выходит намного быстрее чем pat::trie_set ?
    http://rsdn.nemerleweb.com
    http://nemerleweb.com
    Re[3]: trie_(map|set), v0.19 и тесты
    От: _nn_ www.nemerleweb.com
    Дата: 12.10.05 08:38
    Оценка:
    Здравствуйте, R.K., Вы писали:


    __>>Что-то я недопонял тест, hash_set получается выходит намного быстрее чем pat::trie_set ?


    RK>Да. Но hash_set предполагается сравнивать с trie_set<DelusionString>:


    Все встало на места

    Что-то я не нахожу файлы тестов, куда вы их выложили ?
    Я хочу сравнить trie_map/set с hash_/map/set от STLPort.
    http://rsdn.nemerleweb.com
    http://nemerleweb.com
    Re[4]: trie_(map|set), v0.19 и тесты
    От: _nn_ www.nemerleweb.com
    Дата: 12.10.05 09:06
    Оценка:
    Здравствуйте, _nn_, Вы писали:

    __>Здравствуйте, R.K., Вы писали:



    __>>>Что-то я недопонял тест, hash_set получается выходит намного быстрее чем pat::trie_set ?


    RK>>Да. Но hash_set предполагается сравнивать с trie_set<DelusionString>:


    __>Все встало на места


    __>Что-то я не нахожу файлы тестов, куда вы их выложили ?

    __>Я хочу сравнить trie_map/set с hash_/map/set от STLPort.

    Вдогонку было бы неплохо использовать один стиль именования, а то у вас их несколько.
    Лучше все в стиле STL
    Кстати лучше не начинать имена с подчеркивания — _Myt, _Pairib, а еще лучше заменить на что-то более информативное, например this_type, pair_type.
    http://rsdn.nemerleweb.com
    http://nemerleweb.com
    Re[5]: trie_(map|set), v0.19 и тесты
    От: R.K. Украина  
    Дата: 12.10.05 09:45
    Оценка:
    Здравствуйте, _nn_, Вы писали:

    __>Здравствуйте, _nn_, Вы писали:


    __>>Здравствуйте, R.K., Вы писали:



    __>>>>Что-то я недопонял тест, hash_set получается выходит намного быстрее чем pat::trie_set ?


    RK>>>Да. Но hash_set предполагается сравнивать с trie_set<DelusionString>:


    __>>Все встало на места


    __>>Что-то я не нахожу файлы тестов, куда вы их выложили ?


    В архиве с 0.19 версией:
    speed_test.cpp
    speed_test_pq.cpp

    __>>Я хочу сравнить trie_map/set с hash_/map/set от STLPort.


    __>Вдогонку было бы неплохо использовать один стиль именования, а то у вас их несколько.


    Всего два: мой текущий (я их сменил уже с полдесятка;) и STL. Там где стиль вылезает на поверхность в виде интерфейсов — там "like STL".

    __>Лучше все в стиле STL


    Со-омнительно... мне он не нравится

    __>Кстати лучше не начинать имена с подчеркивания — _Myt, _Pairib, а еще лучше заменить на что-то более информативное, например this_type, pair_type.


    Это да. Будет время — исправлю.
    You aren't expected to absorb this
    Re[6]: trie_(map|set), v0.19 и тесты
    От: _nn_ www.nemerleweb.com
    Дата: 12.10.05 11:15
    Оценка:
    Здравствуйте, R.K., Вы писали:

    RK>Здравствуйте, _nn_, Вы писали:


    __>>Здравствуйте, _nn_, Вы писали:


    __>>>Здравствуйте, R.K., Вы писали:



    __>>>>>Что-то я недопонял тест, hash_set получается выходит намного быстрее чем pat::trie_set ?


    RK>>>>Да. Но hash_set предполагается сравнивать с trie_set<DelusionString>:


    __>>>Все встало на места


    __>>>Что-то я не нахожу файлы тестов, куда вы их выложили ?


    RK>В архиве с 0.19 версией:

    RK>speed_test.cpp
    RK>speed_test_pq.cpp

    Я не заметил что 0.17 была скачена
    Тогда скоро будут тесты с STLPort

    __>>>Я хочу сравнить trie_map/set с hash_/map/set от STLPort.


    __>>Вдогонку было бы неплохо использовать один стиль именования, а то у вас их несколько.


    RK>Всего два: мой текущий (я их сменил уже с полдесятка;) и STL. Там где стиль вылезает на поверхность в виде интерфейсов — там "like STL".


    __>>Лучше все в стиле STL


    RK>Со-омнительно... мне он не нравится

    Ну дело вкуса

    __>>Кстати лучше не начинать имена с подчеркивания — _Myt, _Pairib, а еще лучше заменить на что-то более информативное, например this_type, pair_type.


    RK>Это да. Будет время — исправлю.

    Ждем-с
    http://rsdn.nemerleweb.com
    http://nemerleweb.com
    Re: trie_(map|set), v0.19 и тесты
    От: _nn_ www.nemerleweb.com
    Дата: 12.10.05 14:08
    Оценка:
    Здравствуйте, R.K., Вы писали:

    Кстати версия 0.19 не компилируется с STLPort, так как в нем нет hash_compare.
    http://rsdn.nemerleweb.com
    http://nemerleweb.com
    Re[2]: trie_(map|set), v0.19 и тесты
    От: R.K. Украина  
    Дата: 13.10.05 05:47
    Оценка:
    Здравствуйте, _nn_, Вы писали:

    __>Здравствуйте, R.K., Вы писали:


    __>Кстати версия 0.19 не компилируется с STLPort, так как в нем нет hash_compare.


    Просто закомментировать объявление и употребление string_hash_compare и всё должно пройти. Там и так очень классная хеш-функция, судя по тестам:
    --------------- STLPort
    
    
    MSVC 7.1 + STLPort 4.6.2 (PAT_SHORTCUTS) File: 93 М (russian words)
    
    *** pat::trie_set
    Timer: 20.7815 sec.
    Size: 404463
    Timer erase: 10.9516 sec.
    *** pat::trie_set<pat::DelusionString>
    Timer: 19.1018 sec.
    Size: 404463
    *** std::set
    
    
    MSVC 7.1 + STLPort 4.6.2 (w/o PAT_SHORTCUTS) File: 93 М (russian words)
    
    *** pat::trie_set
    Timer: 21.983 sec.
    Size: 404463
    Timer erase: 0.156297 sec.
    *** std::set
    Timer: 53.3017 sec.
    Size: 404463
    Timer erase: 0.140155 sec.
    *** std::hash_set
    Timer: 10.9704 sec. // поражает воображение!
    Size: 404463
    *** std::hash_set: to std::vector + std::sort
    Timer: 1.44805 sec.
    Size: 404463
    Full hash timer: 12.4185 sec.


    (с ухмылкой) Однако, всё та же "посредственная реализация", выбранная мною изначально (см. stlport\stl\_hash_fun.h):
    inline size_t __stl_hash_string(const char* __s)
    {
      _STLP_FIX_LITERAL_BUG(__s)
      unsigned long __h = 0; 
      for ( ; *__s; ++__s)
        __h = 5*__h + *__s;
      
      return size_t(__h);
    }


    Видимо-таки реализация самой хеш-тейбл удачная.
    You aren't expected to absorb this
    trie_(map|set), v0.23 и дерево суффиксов
    От: R.K. Украина  
    Дата: 18.10.05 20:05
    Оценка:
    Привет всем!

    trie_(map|set), v0.23 (MSVC 7.1, GCC 3.2, Comeau 4.3):

    Теперь о самом интересном.
    В анонсе суффиксных деревьев
    Автор: R.K.
    Дата: 05.10.05
    говорится:

    контейнеры, аналогичные суффиксным деревьям (время добавления очередного суффикса константно)

    Насчёт времени добавления я немного ошибся: на самом деле сложность индексирования всей строки линейно зависит от её длины; получается, что добавление одного суффикса константно лишь в среднем.
    Но это не меняет сути дела: в текущую версию дополнительных контейнеров STL добавлен прототип шаблона контейнера suffix_set, реализующий динамическое суффиксное дерево на основе PATRICIA trie.
    Интерфейс suffix_set:
    // constructor
    suffix_set(
        key_type keys,            // передается указатель на начало суффиксного буфера (на первый суффикс)
        size_type size,            // размер окна (одновременно индексируемых суффиксов)
        const bit_compare &bitComp=bit_compare(),
        const allocator_type &alloc=allocator_type());
    // в конструкторе сразу добавляется первый узел, он же root
    // инвариант для suffix_set: в контейнере должен быть хотя бы один суффикс
        
    // вставляем очередной суффикс (следующий по возрастанию адресов)
    void push_back();
    
    // удаляем наиболее ранний суффикс (первый по порядку добавления)
    void pop_front(); // именно этот метод делает suffix_set динамическим
    
    bool full() const; // true, если нет места для добавления очередного суффикса
    
    size_type size() const; // возвращает текущее кол-во суффиксов

    Подобный интерфейс, привычный для очереди (push_back/pop_front), обусловлен необходимостью последовательного (без пропусков) добавления/удаления суффиксов, а также произвольного доступа (по порядку добавления) к узлам дерева и к соответствующим им суффиксам, которые играют роль ключей.
    Для реализации внутреннего хранения узлов цифрового дерева был выбран кольцевой буфер (см. ring_buffer.h), как структура, свойства которой наиболее соответствуют требованиям дерева суффиксов.
    Требования к памяти: <индексируемая строка> + <размер окна suffix_set>*sizeof(Node). sizeof(Node) при включенном PAT_ALIGNHACK составляет 16 байт.
    Все возможности, доступные в trie_set, доступны и в suffix_set (пока есть const_(iterator|partimator)), за исключением тех, которые основаны на конечности ключей, ибо суффиксы обязаны быть бесконечными. Ещё одно отличие состоит в том, что, во избежание избыточности, в узлах suffix_set не хранятся указатели на суффиксы, но на внешнем интерфейсе это не отражается.
    Пример работы с suffix_set см. test_suffix.cpp. На моём P4-2.8 1G RAM файл в 33М полностью индексировался (размер окна == длине файла) за 18 сек., исп. ОЗУ — 570М. Файл 75М с окном 4М динамически индексировался за 60 сек., исп. ОЗУ — 140М.
    Основной причиной реализации этого прототипа была необходимость проверить, будут ли работать мои, до сей поры чисто умозрительные, алгоритмы на практике. Так что ещё предстоит довести дерево суффиксов до нормального состояния, обобщив его с trie_* контейнерами, сделать suffix_map (трудно представить, как его можно использовать, но на всякий случай;). Также стоит подумать об уменьшении объема используемого ОЗУ и об алгоритмах, эффективно реализуемых с suffix trie.
    You aren't expected to absorb this
    (trie_|suffix_)(map|set), v0.29, long-expected ;)
    От: R.K. Украина  
    Дата: 03.11.05 12:16
    Оценка:
    Привет всем!

    (trie_|suffix_)(map|set), v0.29 (MSVC 7.1, GCC 3.2, Comeau 4.3).
    Пришлось переписать заново практически всё. По основным пунктам:
    You aren't expected to absorb this
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.