Russian Qt Forum

Программирование => С/C++ => Тема начата: alexis031182 от Июль 09, 2012, 13:35



Название: [РЕШЕНО] Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 09, 2012, 13:35
Добрый день.

Имеется реализация ассоциативного контейнера на базе std::vector<std::pair<Key, Value>>, предложенная Александреску. Условия использования таковы, что скорость изменения контейнера (добавление, удаление элементов) абсолютно не критична. Критична скорость поиска элемента. Также, обязательна поддержка уникальности ключей.

Встречались ли вам другие аналогичные контейнеры, но с иным подходом в реализации?

А может быть имеются собственные наработки? В таком случае, если позволяется и/или имеется возможность, поделитесь идеей или просто основными моментами реализации.

Спасибо.

З.Ы. На всякий случай во вложении AssocVector


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: m_ax от Июль 09, 2012, 13:58
А чем std::map не устраивает как ассоциативный контейнер?
Скорость поиска там, как Log(n). (n - число элементов)
http://www.cplusplus.com/reference/stl/map/ (http://www.cplusplus.com/reference/stl/map/) 


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 09, 2012, 14:14
AssocVector ищет со скоростью O(n)


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: mutineer от Июль 09, 2012, 14:18
AssocVector ищет со скоростью O(n)

Так это медленнее, чем логарифм. Или скорость критична в том смысле, что чем медленнее, тем лучше?


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 09, 2012, 14:35
AssocVector ищет со скоростью O(n)
Нет, там дело сводится к std::lower_bound, а это тот же логарифм.

Вообще логарифм поиска - совсем неплохая скорость. Быстрее хеш (если дать побольше корзин). Но тут выплывает нужно ли последовательное итерирование? Если необходимо четко выдавать элементы по возрастанию - тогда, на мой взгляд, огород себя не оправдывает - проще std::map или std::set.

Однако есть интересный частный случай - можно выдавать "в порядке корзин", напр
bin1 - values 0.0....0.1
bin2 - values 0.1....0.2
и.т.д

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


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 09, 2012, 14:39
Так это медленнее, чем логарифм. Или скорость критична в том смысле, что чем медленнее, тем лучше?
Ошибся. O(n) у него скорость изменения, то есть медленнее, нежели чем у map'а.

Нужен быстрый поиск элемента.

Вот нашёл хоть какой-то перевод:
Цитировать
Значительное усовершенствование заключается в следующем: тип std::map изменен и стал более эффективным. Мэтт Остерн (Matt Austern, 2000) выяснил, что стандартные ассоциативные контейнеры имеют более узкую область применения, чем принято думать. В частности, упорядоченный вектор в сочетании с алгоритмами бинарного поиска (например, алгоритмом std::lower_bound) может оказаться намного эффективнее, чем ассоциативный контейнер. Это происходит, когда количество обращений к его элементам намного превышает количество вставок. Итак, следует внимательно изучить ситуации, в которых обычно применяются объекты двойного диспетчера. Чаще всего двойной диспетчер представляет собой структуру, в которую информация редко записывается, но из которой часто считывается. Обычно программа лишь однажды устанавливает обратные вызовы, а затем очень часто использует диспетчер. Это хорошо согласуется с использованием механизма виртуальных функций, который дополняется двойным диспетчером. Решение о том, какие функции являются виртуальными, а какие нет, принимается на этапе компиляции.

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

Библиотека Loki предусматривает использование упорядоченных векторов, определяя шаблонный класс Assocvector, который может заменять собой класс std::map (он содержит те же самые функции-члены), реализованный на основе класса std::vector. Класс Assocvector отличается от класса std::map функциями erase (функции Assocvector::erase аннулируют все итераторы внутри объекта). Кроме того, функции-члены insert и erase усложнены (выполняя операции вставки и удаления за линейное, а не постоянное время). Поскольку класс Accosvector совместим с классом std::map, для описания структуры, содержащейся в двойном диспетчере, мы будем также использовать термин ассоциативный массив (тар).


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 09, 2012, 14:47
Нет, там дело сводится к std::lower_bound, а это тот же логарифм.

Вообще логарифм поиска - совсем неплохая скорость. Быстрее хеш (если дать побольше корзин). Но тут выплывает нужно ли последовательное итерирование? Если необходимо четко выдавать элементы по возрастанию - тогда, на мой взгляд, огород себя не оправдывает - проще std::map или std::set.
Нужен поиск произвольного элемента по ключу для фабрики.

Однако есть интересный частный случай - можно выдавать "в порядке корзин", напр
bin1 - values 0.0....0.1
bin2 - values 0.1....0.2
и.т.д

Тогда можно сделать и поиск пошустрее и вставка очень быстрая и расход памяти ноль  :)
Здесь можно более понятно? Или быть может есть ссылка на что почитать :)


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 09, 2012, 15:09
Здесь можно более понятно? Или быть может есть ссылка на что почитать :)
Принцип QHash с reserve имеется ввиду?


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 09, 2012, 16:16
Сделал тест на поиск 100000 элементов в 100000 контейнере, представлявшем в свою очередь три варианта: AssocVector, QHash и std:map. Усреднённый результат нескольких прогонов:

QHash - 30 ms.
AssocVector - 38 ms.
std::map - 48 ms.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 09, 2012, 16:52
Мэтт Остерн правильно пишет, но не все - массив часто требует намного меньше памяти.

Нужен поиск произвольного элемента по ключу для фабрики.
Здесь просто QHash. Пусть ключ - строка, число элементов напр 100. Тогда на поиске (грубо)

- хеш = 1 вычисление хеш-ключа + 1 сравнение
- мап = 6-8 сравнений

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


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 09, 2012, 17:15
Спасибо. Получается, QHash - лучший вариант пока. Интересно, а если попробовать применить несколько более быстрый алгоритм для вычисления хэша, нежели тот, что использует QHash?.. Я не проводил сравнения ещё, но давно хотелось сравнить его с superfasthash. Вот и повод. Просто помудрить здесь чего-нибудь эксперимента ради...


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 09, 2012, 17:40
Спасибо. Получается, QHash - лучший вариант пока. Интересно, а если попробовать применить несколько более быстрый алгоритм для вычисления хэша, нежели тот, что использует QHash?.. Я не проводил сравнения ещё, но давно хотелось сравнить его с superfasthash. Вот и повод. Просто помудрить здесь чего-нибудь эксперимента ради...
Можно, но там много не отбить. QHash сохраняет хеш-ключ для каждого элемента, т.е. считается только подаваемый. Если строки не слишком длинные - игра свеч не стоит.

Не нужно что-то оптимизировать "не будучи вынужденным". Др словами сначала надо иметь работающую конструкцию, и затем уже искать узкие места и улучшать. А иначе "здесь гоняемся за микронами" а в др месте тратим время вагонами  :)


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 09, 2012, 18:00
Можно, но там много не отбить. QHash сохраняет хеш-ключ для каждого элемента, т.е. считается только подаваемый. Если строки не слишком длинные - игра свеч не стоит.
Попробовал, так и есть - никакой разницы :D

Не нужно что-то оптимизировать "не будучи вынужденным". Др словами сначала надо иметь работающую конструкцию, и затем уже искать узкие места и улучшать. А иначе "здесь гоняемся за микронами" а в др месте тратим время вагонами  :)
У меня в фабрике (пока ещё без пула/кеша) вроде всё на шаблонах, там больше вроде нечему быть затратным, всё в compile-time идёт, окромя конечно операции создания фабрикой запрашиваемого объекта (единственный new). И вот одним узким местом по сути является поиск класса объекта по ключу.

Надо как-то строку умудриться в шаблон прикрутить. То есть превратить её в тип. В Интернете есть примеры, попробую покопаться.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 10, 2012, 17:49
Надо как-то строку умудриться в шаблон прикрутить. То есть превратить её в тип. В Интернете есть примеры, попробую покопаться.
Мало чего оптимистичного. Строка в шаблон загоняется, но она должна быть константной. Да в принципе это и логично.

Попробовал соорудить самопальный контейнер, где ключами выступают исключительно строки (оно мне для фабрики, в принципе, так и надо будет). В рассуждениях исходил из того, что ключи по значениям своим довольно близки. Например: "AWebRequestGet", "AWebRequestPost", и т.п. Подумал, что имеет смысл сравнивать посимвольно, а в контейнере хранить элементы в виде дерева символов. Таким образом, получилось добиться ускорения чуть более 10 мс по сравнению с QHash в том же тесте и с теми же условиями, что написал ранее:
Сделал тест на поиск 100000 элементов в 100000 контейнере, представлявшем в свою очередь три варианта: AssocVector, QHash и std:map. Усреднённый результат нескольких прогонов:

QHash - 30 ms.
AssocVector - 38 ms.
std::map - 48 ms.
Новый контейнер делает за 15-20 мс.

Код во вложении. Не оптимизирован, имеет варнинги, публикую просто для обсуждения идеи.

Пример использования:
Код
C++ (Qt)
#include "acompositecontainer.h"
 
AComposite::Container<QString> container;
container.insert("tra", QString("tra-ta-ta"));
qDebug() << container.value("tra");
 


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 10, 2012, 18:57
Если я не ошибся, и алгоритм, реализованный этим кодом стоящий, то можно его превратить в настоящий read only контейнер, регистрацию элементов выполнять в compile-time. Для фабрики будет то, что нужно, т.к. классы как правило регистрируются при старте приложения и пользуются в неизменном виде до окончания сессии его работы. Таким образом, получится мгновенная вставка и довольно быстрый lookup. Такая задумка.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 10, 2012, 19:30
Если я не ошибся, и алгоритм, реализованный этим кодом стоящий, то можно его превратить в настоящий read only контейнер, регистрацию элементов выполнять в compile-time. Для фабрики будет то, что нужно, т.к. классы как правило регистрируются при старте приложения и пользуются в неизменном виде до окончания сессии его работы. Таким образом, получится мгновенная вставка и довольно быстрый lookup. Такая задумка.
Ага, в ход пошли автоматы/пулеметы. Не навязываю своего мнения, но Вас определенно "заносит в абстракцию" :)  Лучше смотреть с точки зрения прикладной задачи. Ну напр чего это Вам так остро нужен поиск по имени (строке)? Не лучше ли работать по ID - тогда можно спрыгнуть на индекс - быстрее любой мапы. Мне кажется Вы уперлись в слишком узкое место - QHash вполне хорош, ну и чего мудрить? Поехали дальше, прикладная задача предоставляет гораздо больший простор для фантазии и той же оптимизации чем абстрактные схемы которые, как правило, хорошо известны, и которые Вы упорно хотите обобщить :). Вот примерчик из моей практики http://www.prog.org.ru/index.php?topic=21228.msg145940#msg145940 (http://www.prog.org.ru/index.php?topic=21228.msg145940#msg145940)


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 10, 2012, 20:17
Ага, в ход пошли автоматы/пулеметы. Не навязываю своего мнения, но Вас определенно "заносит в абстракцию" :)  Лучше смотреть с точки зрения прикладной задачи.
Я бы и не задумывался, если бы не условия вполне себе прикладной задачи.

Ну напр чего это Вам так остро нужен поиск по имени (строке)?
А как иначе, если на входе она и только она?

Не лучше ли работать по ID - тогда можно спрыгнуть на индекс - быстрее любой мапы.
"ID" имеется ввиду тип? Да я во сне уже вижу, как я тип на входе получил и прыгаю от радости. К сожалению, только строка.

Мне кажется Вы уперлись в слишком узкое место - QHash вполне хорош, ну и чего мудрить?
Ну как чего? Условия задачи говорят о том, что поиск будет выполняться зверски часто. 10000 сетевых запросов в секунду как минимум должно обрабатываться. И получение из фабрики объекта, ассоциированного со строкой, полученной в свою очередь по HTTP-протоколу, есть тот код, который существенно влияет на весь процесс. Другие подзадачи, которые входят в это же, критичное по времени исполнения место, я точно также буду "долбить" до потери пульса (надеюсь, что только своего). Пока же остановился на этой задаче.

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

Поехали дальше, прикладная задача предоставляет гораздо больший простор для фантазии и той же оптимизации чем абстрактные схемы которые, как правило, хорошо известны, и которые Вы упорно хотите обобщить :).
Как я и сказал, я занимаюсь вполне конкретной задачей. Конкретнее некуда. Да, мне нравятся абстракции, это Вы верно подметили, и в коде своего приложения, возможно где-то перехимичил, но ничего, проект я уже раз двадцать переписывал, перепишу снова, если будет необходимость. И если алгоритм, реализацию которого я предложил, хорошо известен, то наверняка существует код, кем-то уже опубликованный. У Вас есть что-нибудь на заметке? Или натолкните на идею, которая подскажет, как можно выполнить поиск по строке быстрее, чем то, что я предложил.

Вот примерчик из моей практики http://www.prog.org.ru/index.php?topic=21228.msg145940#msg145940 (http://www.prog.org.ru/index.php?topic=21228.msg145940#msg145940)
Да, я видел эту тему. Вроде даже понял выдвинутую Вами там идею. Но не могу пока сообразить, как это я могу использовать у себя.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 10, 2012, 20:49
И если алгоритм, реализацию которого я предложил, хорошо известен, то наверняка существует код, кем-то уже опубликованный. У Вас есть что-нибудь на заметке? Или натолкните на идею, которая подскажет, как можно выполнить поиск по строке быстрее, чем то, что я предложил.
Ничего полезного навскидку не помню. По сути, как я понимаю, Ваш последний вариант приближается к автомату. который работает примерно так.

- получили строку, первый символ напр "а", это число - индекс в таблице. Перешли по этому индексу (без всяких поисков) просто table[int('a')]

- следующий символ напр "b", опять прыгнули по индексу, как бы table[int('a')][int('b')]
...
в итоге извлекли что-то table[int('a')][int('b')][int('c')]. Конечно это будет быстрее любого поиска, но надо как-то намутить все эти таблицы. Это не так сложно как кажется, и памяти они сожрут хоть и прилично но не вагон. Хотите этим заниматься - дело Ваше.

"ID" имеется ввиду тип? Да я во сне уже вижу, как я тип на входе получил и прыгаю от радости. К сожалению, только строка.
ID - просто уникальное число. Ну допустим на входе только строка - но при помещении ее в мапу никто не мешает присвоить ей ID и это ID вернуть, И во всех последующих операциях использовать это число которое есть прямой индекс в массиве(ах).


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 10, 2012, 21:21
Ничего полезного навскидку не помню. По сути, как я понимаю, Ваш последний вариант приближается к автомату. который работает примерно так.

- получили строку, первый символ напр "а", это число - индекс в таблице. Перешли по этому индексу (без всяких поисков) просто table[int('a')]

- следующий символ напр "b", опять прыгнули по индексу, как бы table[int('a')][int('b')]
...
в итоге извлекли что-то table[int('a')][int('b')][int('c')].
По сути, я взял идею паттерна композит, который представляет из себя дерево элементов. Просто у меня каждый элемент такого дерева - символ (char). Да, Вы описали похоже.

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

Это не так сложно как кажется, и памяти они сожрут хоть и прилично но не вагон.
"sizeof(letter)", где letter является элементом контейнера и содержит только char, плюс список указателей на такие же, но дочерние letter'ы. Значение же, которое является искомым, содержится лишь на самом верху в иерархии дерева. Поэтому не такой уж и большой перерасход.

Хотите этим заниматься - дело Ваше.
Есть вариант лучше? Или Вы имеете в виду, что не стоит овчинка выделки? Тест показал, что стоит. Даже если бы я выиграл здесь 1 мс, то и тогда стоило бы. Это важно, потому что критично именно в той части задачи, о которой я сказал выше. В любом другом случае я конечно бы озадачился вопросом целесообразности столь длительных потугов на поиск наиболее оптимального решения.

ID - просто уникальное число. Ну допустим на входе только строка - но при помещении ее в мапу никто не мешает присвоить ей ID и это ID вернуть, И во всех последующих операциях использовать это число которое есть прямой индекс в массиве(ах).
Это так, но при условии, что не потребуется снова вычислять ID. А почему его потребуется вычислять? Просто потому, что от сетевого клиента придёт строка, содержащая команду, и из которой и надо будет снова вычислить этот ID. Другими словами, ID, как бы я его не использовал, время жизни его ограничено одним сетевым запросом одного из сетевых клиентов. Новый запрос - новое вычисление ID, хотя по сути, оно практически одно и то же и может иметь лишь значение в пределах известных заранее вариантов (для простоты примера пока исключим возможность отсылки заведомо ложных команд вредителей).


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: DmitryM от Июль 10, 2012, 22:40
Ничего полезного навскидку не помню. По сути, как я понимаю, Ваш последний вариант приближается к автомату. который работает примерно так.

- получили строку, первый символ напр "а", это число - индекс в таблице. Перешли по этому индексу (без всяких поисков) просто table[int('a')]

- следующий символ напр "b", опять прыгнули по индексу, как бы table[int('a')][int('b')]
...
в итоге извлекли что-то table[int('a')][int('b')][int('c')].
По сути, я взял идею паттерна композит, который представляет из себя дерево элементов. Просто у меня каждый элемент такого дерева - символ (char). Да, Вы описали похоже.

Конечно это будет быстрее любого поиска, но надо как-то намутить все эти таблицы.
А чего их "мутить"? По моему вполне просто любой длины строка вписывается в такое дерево. "Двигаясь" по его веткам, можно легко восстановить строку. При этом, если символы одинаковы на одном порядковом уровне, то хранятся в контейнере они в единичном экземпляре. Но это само собой понятно.
Плохо вечером соображаю, вы тут префиксное дерево (http://habrahabr.ru/post/111874/) изобретаете?


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 10, 2012, 22:48
Плохо вечером соображаю, вы тут префиксное дерево (http://habrahabr.ru/post/111874/) изобретаете?
Ага ;D Спасибо, теперь буду знать, что это trie (странное написание, если tree - дерево; хотя гугл даёт перевод для trie как "синтаксическое дерево").


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 10, 2012, 23:05
DmitryM, какой реализацией пользуешься? Можешь порекомендовать? Интересует именно скорость поиска элемента, остальное вторично.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 11, 2012, 11:24
А чего их "мутить"? По моему вполне просто любой длины строка вписывается в такое дерево. "Двигаясь" по его веткам, можно легко восстановить строку.
Разница в том что Вы тратите время на цикл for

Даже если бы я выиграл здесь 1 мс, то и тогда стоило бы. Это важно, потому что критично именно в той части задачи, о которой я сказал выше
У человека не знакомого с задачей это вызывает большие сомнения. Что это за обработка если ее время соразмеримо с обращением в хеш по ключу? Не теряется ли больше времени напр на перегонку QString в char *? Или просто на создание QString? Впрочем Вам виднее


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: DmitryM от Июль 11, 2012, 12:02
Разница в том что Вы тратите время на цикл for
В случае с QMap тратиться время на сравнение ключей.
В случае с QHash тратится время на вычисления ключа.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 11, 2012, 12:04
Разница в том что Вы тратите время на цикл for
DmitryM привёл ссылку на статью, где перечисляются методы по оптимизации подобных деревьев. Они могут значительно сократить количество итераций в циклах (зависит конечно от содержимого ключей, но в моей задаче это как раз присутствует). Попытаюсь реализовать.

У человека не знакомого с задачей это вызывает большие сомнения. Что это за обработка если ее время соразмеримо с обращением в хеш по ключу?
Высоконагруженный HTTP-сервер. Необходима поддержка большого количества запросов сетевых клиентов (10000 в секунду - программа-минимум). Поскольку условия задачи предполагают постоянное обращение к хешу по ключу (о невозможности использования ранее вычисленных указателей на элементы хеша я расписал выше), то естественно желание (если такая возможность существует, а она существует) оптимизировать данную часть кода.

Конечно, если бы это было, например, 100 - 1000 запросов, то намерения мои были бы не оправданы. Но временные потери становятся явно ощутимы при многократном увеличении одновременно поступающих сетевых запросов. Для реализации хотя бы просто поддержки такого количества сетевых соединений (не говоря уже о передачи данных) пришлось отказаться от того же QTcpServer, заменив его epoll'ом (меня, всё же, сервер интересует именно под Linux).

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

Не теряется ли больше времени напр на перегонку QString в char *? Или просто на создание QString? Впрочем Вам виднее
QString уже ликвидирован во многих частях кода. И будет убран полностью постепенно. На входе из сети я получаю char*. Именно его я называл "строкой".


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 11, 2012, 12:15
DmitryM привёл ссылку на статью, где перечисляются методы по оптимизации подобных деревьев. Они могут значительно сократить количество итераций в циклах (зависит конечно от содержимого ключей, но в моей задаче это как раз присутствует). Попытаюсь реализовать.
Ну и реализуйте автоматом (таблицей) если это так критично

пришлось отказаться от того же QTcpServer
Это к слову (оффтоп), но возможно замена QMutex на атомарный лок (а еще лучше tbb::spin_mutex) будет "само то". 


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 11, 2012, 12:29
Ну и реализуйте автоматом (таблицей) если это так критично
Это какое-то альтернативное дереву решение? Можно подробнее?

Это к слову (оффтоп), но возможно замена QMutex на атомарный лок (а еще лучше tbb::spin_mutex) будет "само то". 
Спасибо. Мне интересна в общем-то любая замена для кьют-классов, если она будет гарантированно быстрее, и конечно, если эта часть кода выполняется в обработчике сетевых запросов. Но так как мне сразу всё не охватить, я разбил оптимизацию на подзадачи. Но всё же к тредам, думаю, вернусь очень скоро.

С dev-решениями от Intel ещё не приходилось работать. Хотел у них для Linux профилировщик бесплатно заполучить, отправил кучу запросов с их сайта, но что-то нет ответа. Помню, когда-то это прокатило, прислали письмо со ссылкой на программу, но не сохранилась её копия. Жаль.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: DmitryM от Июль 11, 2012, 12:55
Это к слову (оффтоп), но возможно замена QMutex на атомарный лок (а еще лучше tbb::spin_mutex) будет "само то". 
В qmutex_unix.cpp, там
Код
C++ (Qt)
#elif defined(Q_OS_LINUX)
# include <linux/futex.h>
# include <sys/syscall.h>
# include <unistd.h>
# include <QtCore/qelapsedtimer.h>
#endif
 
Так что QMutex в Linux и так быстрый.
Так что


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 11, 2012, 13:18
Это какое-то альтернативное дереву решение? Можно подробнее?
Ну то самое что я писал. Напр первый шаг (первый символ строки) - имея на руках все возможные строки Вы знаете диапазон, пусть 'a'..'t'. Делаете таблицу указателей на ноды, для того чтобы прыгать на нужный элемент сразу (без for), индекс получаете просто отняв 'a' (минимальный возможный) от первого символа. Если индекс < 0 или больше размера таблицы или указатель ноль - ну значит такого нет. Потом все то же для второго символа (уже используя данные нода на который перешли). На первый взгляд это ужасно - море таблиц, но на деле число вариантов невелико. 

Спасибо. Мне интересна в общем-то любая замена для кьют-классов, если она будет гарантированно быстрее, и конечно, если эта часть кода выполняется в обработчике сетевых запросов. Но так как мне сразу всё не охватить, я разбил оптимизацию на подзадачи. Но всё же к тредам, думаю, вернусь очень скоро.
Сделайте просто контейнер и читайте/пишите его под защитой QMutex. Или просто читайте - только зациклите защищенное чтение. Сравните время на 1 и напр 4 нитках. Этот тест займет полчаса но, во всяком случае на posix, возможно Вы будете удивлены (не скажу что "приятно удивлены")


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 11, 2012, 13:56
Ну то самое что я писал. Напр первый шаг (первый символ строки) - имея на руках все возможные строки Вы знаете диапазон, пусть 'a'..'t'. Делаете таблицу указателей на ноды, для того чтобы прыгать на нужный элемент сразу (без for), индекс получаете просто отняв 'a' (минимальный возможный) от первого символа. Если индекс < 0 или больше размера таблицы или указатель ноль - ну значит такого нет. Потом все то же для второго символа (уже используя данные нода на который перешли). На первый взгляд это ужасно - море таблиц, но на деле число вариантов невелико. 
То есть использовать не QList для хранения нодов, а массив, индексами которого будут являться порядковые номера букв? Очень интересно. Спасибо.

Сделайте просто контейнер и читайте/пишите его под защитой QMutex. Или просто читайте - только зациклите защищенное чтение. Сравните время на 1 и напр 4 нитках. Этот тест займет полчаса но, во всяком случае на posix, возможно Вы будете удивлены (не скажу что "приятно удивлены")
Обязательно так и сделаю, поскольку мне интересно сравнивать производительность различных решений. Пожалуй займусь этим сразу, как закончу с фабрикой.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 11, 2012, 14:01
...
Так что QMutex в Linux и так быстрый.
Всё же интересно посмотреть в сравнении. К тому же мой комп с интеловским процессором, и, возможно, решение Intel использует аппаратную часть по полной. Впрочем, я не сильно разбираюсь в этом, чтобы судить. Так или иначе, потестить можно. Тестировать конечно буду только Linux-субъективно.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 11, 2012, 14:19
То есть использовать не QList для хранения нодов, а массив, индексами которого будут являться порядковые номера букв?
Ну да. Конечно если for перебирает 2-3 элемента, то выигрыш незначителен. А вот если хотя бы 10 - уже ощутимо.

Всё же интересно посмотреть в сравнении. К тому же мой комп с интеловским процессором, и, возможно, решение Intel использует аппаратную часть по полной. Впрочем, я не сильно разбираюсь в этом, чтобы судить. Так или иначе, потестить можно. Тестировать конечно буду только Linux-субъективно.
Intel tbb поддерживает не только процессор Intel. Однако сразу тащить библиотеку совсем необязательно, атомарный лок несложно реализовать хотя бы средствами Qt. Важно что "честный" QMutex (на базе pthread_..) может оказаться капитальным "узким местом" как это случилось для одного моего проекта.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 11, 2012, 14:33
Ну да. Конечно если for перебирает 2-3 элемента, то выигрыш незначителен. А вот если хотя бы 10 - уже ощутимо.
Согласен с Вами, спасибо за очень дельный совет. Так и сделаю.

Intel tbb поддерживает не только процессор Intel.
А-а... тогда хорошо.

Однако сразу тащить библиотеку совсем необязательно, атомарный лок несложно реализовать хотя бы средствами Qt.
Я пока даже близко не подходил к этой теме.

Важно что "честный" QMutex (на базе pthread_..) может оказаться капитальным "узким местом" как это случилось для одного моего проекта.
Хм... тогда без вариантов. Буду знакомиться с этой темой.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 11, 2012, 15:05
Ну вот, ещё 5 мс отбил на тесте. Спасибо, Игорь :)


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: DmitryM от Июль 11, 2012, 15:12
Важно что "честный" QMutex (на базе pthread_..) может оказаться капитальным "узким местом" как это случилось для одного моего проекта.
Не все так однозначно (http://www.alexonlinux.com/pthread-mutex-vs-pthread-spinlock)
Тот же pthread_mutex может не использовать системных вызовов ОС.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 19, 2012, 14:23
Пришёл к выводу, что Trie не подходит мне. Ключей не слишком много, и некоторые из них значительно отличаются друг от друга. В общем, стал разбираться с хэшем.

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

Нарисовал следующее:
Код
C++ (Qt)
//! Функция возврата элемента.
const ValueType &value(const char *key) const {
  size_t   key_len  = strlen(key);
  uint32_t key_hash = calcHash(key, key_len);
 
  if(!_p_units.isEmpty()) {
     int val = (key_hash & 0x000000ff);
     int index = val - _lvl_min[0];
 
     //if(index >= 0/* && index < _p_units.size()*/) {
        const QVector<QVector<QVector<Unit*> > > &p_units1 = _p_units.at(index);
        val = (key_hash & 0x0000ff00) >> 8;
        index = val - _lvl_min[1];
 
        //if(index >= 0/* && index < p_units1.size()*/) {
           const QVector<QVector<Unit*> > &p_units2 = p_units1.at(index);
           val = (key_hash & 0x0000ff00) >> 16;
           index = val - _lvl_min[2];
 
           //if(index >= 0/* && index < p_units2.size()*/) {
              const QVector<Unit*> &p_units3 = p_units2.at(index);
              val = (key_hash & 0x0000ff00) >> 24;
              index = val - _lvl_min[3];
 
              //if(index >= 0/* && index < p_units3.size()*/) {
                 return p_units3.at(index)->value();
              //}
           //}
        //}
     //}
  }
 
  return ValueType();
}
 
В приведённом листинге закомментированы условия, позволяющие определить, входит ли вычисленный индекс запрашиваемого элемента в каждый из контейнеров, формирующих иерархию хэшей ключей. Если я выполняю код в таком виде, подставляя в функцию заведомо корректные ключи, то на 100000*41 (41 - кол-во элементов в контейнере) вызовов я получаю время 5 мс. Но стоит только открыть условия проверки хотя бы просто на "index >= 0", то время выполнения теста с теми же параметрами увеличивается до 96 мс. Если же полностью раскомментировать код, то вплоть до 160 мс.

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


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 19, 2012, 15:13
Что интересно: QHash выполняется в этом тесте за 70 мс. Для получения элемента использовал его функцию at(), которая предполагает отсутствие проверки на корректность ключа (это из Ассистента, да и практикой подтверждено).

Разница в 65 мс - это просто мечта!

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


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 19, 2012, 15:20
Во все детвли не вникал, но идею понял. Имеет смысл сделать первые 4 таблицы по 256 элементов и проверяться на uint index < 256

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

Прошу прощения за навязчивость, но выжимать надо не там где хочется, а там где "тормозит больше всего".


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 19, 2012, 17:13
Ёлки! Только собрался ответ написать, как объявили эвакуацию всего дома (панельная 9-ти этажка) из-за возможно заложенного взрывного устройства. Какие-то школьники развлеклись, как оказалось :(

Во все детвли не вникал, но идею понял. Имеет смысл сделать первые 4 таблицы по 256 элементов и проверяться на uint index < 256
Да тут получается, что как только я абсолютно любое условие вставляю - сразу значительное снижение производительности.

Не совсем по теме но может быть важно. Разница 6-96 вызывает большие сомнения. Возможно это эффект процессорного кэша. Вы тестируете "холостой ход", т.е. обращаетесь к одним и тем же участкам памяти. Они кешируются что может создать "иллюзию больших скоростей". Однако если Вы подключите "нагрузку" (т.е. будете что-то делать с извлеченным из хеша значением) то скорость извлечения будет уже совсем другая.
Хорошее замечание. Несомненно что-то тут запоминается машиной. Но провёл проверку - вывод перенаправил в QDebug. Результат:

QHash - 36586 мс
Мой хэш - 35472 мс
Разница - 1114 мс

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


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 19, 2012, 18:57
Несомненно что-то тут запоминается машиной. Но провёл проверку - вывод перенаправил в QDebug. Результат:

QHash - 36586 мс
Мой хэш - 35472 мс
Разница - 1114 мс
Ну QDebug - то жирно. Поставьте нагрузку соразмеримую, напр qRand и/или acos, atan2 а еще лучше - что-нибудь из своей задачи


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 19, 2012, 19:29
Ну QDebug - то жирно. Поставьте нагрузку соразмеримую, напр qRand и/или acos, atan2 а еще лучше - что-нибудь из своей задачи
Разница с qrand составила 50 мс, проверял на int'ах: QHash 150-160 мс, мой - 90-100 мс.

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

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

К сожалению, пока не могу придумать как. Да и вообще непонятно, почему если я добавляю хотя бы самый простой "if", то тут же многократно падает скорость, причём хуже чем у QHash. Совсем это непонятно. Вроде ведь и не compile-time. Как это можно объяснить?

В аттаче - моя машина.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 19, 2012, 19:55
Проблема заключается в том, что может придти зловредный сетевой запрос, где строка-ключ будет содержать недопустимое значение. Другими словами, необходимо проверять, что значение по запрашиваемому ключу реально существует. И проверять быстро.
Поставить обработчики-заглушки во все остальные/пустые элементы таблицы. Заглушка переводит на др байт (если они еще есть) или возвращает "нету"

Да и вообще непонятно, почему если я добавляю хотя бы самый простой "if", то тут же многократно падает скорость, причём хуже чем у QHash. Совсем это непонятно. Вроде ведь и не compile-time. Как это можно объяснить?
Где-то удачно "легло на конвейер".


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 19, 2012, 20:09
Поставить обработчики-заглушки во все остальные/пустые элементы таблицы. Заглушка переводит на др байт (если они еще есть) или возвращает "нету"
Запрашиваемый индекс может не только попадать в диапазоне на пустой указатель в таблице, но и выходить за пределы диапазона. Тут сразу приложение упадёт.

Я сделал по Вашей рекомендации для trie: вычисляю индекс в таблице указателей через минимальный символ, вместо перебора (for). Этот "символ" у меня - хэш. Точнее сказать, часть хэша. Весь хэш индексом массива не сделать, поэтому я поделил его на четыре части - байта. И вот для каждой из частей храню минимальный байт. Этот минимальный байт вычисляется при каждой вставке нового элемента в контейнер. Соответственно полностью обновляется и вся таблица указателей на элементы. Это всё делает вставку элементов довольно медленной, но мне это не принципиально.

Где-то удачно "легло на конвейер".
Надо проверить на другой машине. Можете помочь с этим? Я подготовлю код и запостю сюда.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 19, 2012, 20:35
Запрашиваемый индекс может не только попадать в диапазоне на пустой указатель в таблице, но и выходить за пределы диапазона.
Каким образом если байт-значения в диапазоне [0..255]?

Надо проверить на другой машине. Можете помочь с этим? Я подготовлю код и запостю сюда.
Не вопрос, но только я в роли "болванчика" :) Т.е. получил файлы, сделал проект из .pro, откомпилил и запустил. Что-то не идет - проблемы Ваши


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 19, 2012, 20:58
Каким образом если байт-значения в диапазоне [0..255]?
Я для экономии (хотя это можно было и не делать) соорудил изменяемый минимальный символ. То есть минимальным может быть и 100, и 111, и 121, и т.п. Таким образом, таблица указателей получается меньше размером. Это конечно заставляет делать перерасчёт таблицы указателей при каждом изменении контейнера, но длительность вставки элементов в контейнер не существенна, если исходить из условий моей задачи.

Не вопрос, но только я в роли "болванчика" :) Т.е. получил файлы, сделал проект из .pro, откомпилил и запустил. Что-то не идет - проблемы Ваши
Спасибо. Я подготовлю полностью .pro . Единственное, я использую функцию для подсчёта длительности затраченного времени gettimeofday(). Я не знаю, есть ли она на виндовсе. Может быть тогда придётся подставить свою.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 19, 2012, 21:10
Вот, готово. Прошу пока не обращать внимания на "грязь" и отсутствие оптимизации, а где-то даже на откровенную глупость в тех участках кода, что не связаны напрямую с задачей. Мне важно было обыграть идею.

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


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 20, 2012, 10:54
Никаких проблем не возникло. Машина Intel Dual Xeon 2.6 (2х2 = 4 ядоа). Mac OSX (posix). Все компиляции в release,

493 (ms) - исходный на gcc 4.2
434  - icc 12.1

Доп тесты (icc)

161 - без qRand
517 - переставил qRand

Цитировать
//        qrand() % c.value(keys.at(i));
       c.value(keys.at(qrand() % keys.size()));

Разница между прогонами (для каждого варианта) незначительна (2-3 ms максимум) 


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 20, 2012, 10:57
Что интересно: QHash выполняется в этом тесте за 70 мс. Для получения элемента использовал его функцию at(), которая предполагает отсутствие проверки на корректность ключа (это из Ассистента, да и практикой подтверждено).
Перегрелся вчера. У QHash нет функции at(). Используется value(). Значит, в 70 мс у QHash входит и проверка на корректность ключа.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 20, 2012, 11:03
...
Разница между прогонами (для каждого варианта) незначительна (2-3 ms максимум) 
Значит дело в машине. Только что попробовал Ваш вариант теста, разница 80-100 мс. Честно говоря, мне это непонятно.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 20, 2012, 11:41
Значит дело в машине. Только что попробовал Ваш вариант теста, разница 80-100 мс. Честно говоря, мне это непонятно.
По-другому ложится в процессорный кеш.

Попробовал незатейливый QHash (переделать на него оказалось очень легко). Получил

31 - sum += c.value(keys.at(i));
300 - qrand() % c.value(keys.at(i));
382 - c.value(keys.at(qrand() % keys.size()));
266 - sum += qrand() % keys.size()
 


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 20, 2012, 12:00
Попробовал незатейливый QHash (переделать на него оказалось очень легко). Получил

31 - sum += c.value(keys.at(i));
300 - qrand() % c.value(keys.at(i));
382 - c.value(keys.at(qrand() % keys.size()));
266 - sum += qrand() % keys.size()
QHash:
44 - "sum += c.value(keys.at(i));"
165 - "qrand() % c.value(keys.at(i));"
186 - "c.value(keys.at(qrand() % keys.size()));"
114 - "sum += qrand() % keys.size();"

Мой хэш:
0 - "sum += c.value(keys.at(i));"
101 - "qrand() % c.value(keys.at(i));"
105 - "c.value(keys.at(qrand() % keys.size()));"
91 - "sum += qrand() % keys.size();"


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 22, 2012, 20:41
...
Мой хэш:
0 - "sum += c.value(keys.at(i));"
...
И стоило только эту сумму вывести в qDebug(), то результат тут же стал 93 мс :(


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 22, 2012, 20:46
Сконструировал ещё один хэш, почитав соответствующую литературу. Но по прежнему не могу добиться превышения скорости над QHash. Самое интересное, что вроде и логика работы контейнера такая же, и алгоритм вычисления хэша строки беру быстрее (или такой же), а результат плачевный.

Стал разбираться с исходниками QHash. Ну практически один к одному, кроме того, что кол-во букетов у QHash формируется хитрым способом. Других различий, вроде, нет. А скорость разница аж в два раза :(


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 23, 2012, 03:13
Непонятно на чем Вы хотите "сыграть". Др словами Вы берете общую задачу ассоциативного поиска и хотите ее ускорить. Так, на мой взгляд, шансов немного - общие вещи хорошо изучены и придумать что-то новое трудно. Ладно уж, общую критику велосипедизма опускаем  :)

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


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 10:37
Непонятно на чем Вы хотите "сыграть". Др словами Вы берете общую задачу ассоциативного поиска и хотите ее ускорить. Так, на мой взгляд, шансов немного - общие вещи хорошо изучены и придумать что-то новое трудно.
Я хочу разобраться, что является ускоряющим фактором у QHash. У меня нет лишних циклов (всё как у QHash), доступ к элементам в массивах по индексу (не QList, просто массив). Логика работы контейнера, вроде бы, одинаковая.

Если беру код вычисления хэша строки из QHash, то мой контейнер работает в 3 раза медленнее. Если беру более быстрый хэш, то в 2 раза медленнее. Значит дело не в хэше. Надо найти причину, из-за которой QHash выигрывает по скорости.

Ладно уж, общую критику велосипедизма опускаем  :)
У меня изначально проект - велосипед (http://www.prog.org.ru/topic_21788_0.html)

Но совсем др дело если бы учитывалась специфика задачи, пусть небольшая загогулина но все же вне общего случая. Вот тогда возможно далеко перегнать общие/стандартные решения. Пока я такой специфики не вижу.
Специфики особой нет. Известно лишь то, что ключей строго определённое кол-во, они не меняются в процессе работы приложения, и само собой изначально известны их значения. Этакий read-only контейнер.

Я пробовал соорудить Hash-контейнер для compile-time. У меня получилось. Я добился такой же скорости, как у QHash. Но там алгоритм вычисления хэша навороченный не вставить, т.к. принцип построения кода совсем другой (впрочем я уверен, что это возможно, просто недостаточно разбираюсь в этой теме). Из-за этого модернизацию compile-time хэша оставил пока до лучших времён.

Пока же задачу поставил разобраться с тонкостями QHash. Что делает его быстрее в сравнении с прямым указанием индексов в массиве?


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 12:05
Может ли скорость lookup'а в QHash быть выше из-за того, что ноды (Node) содержатся выравнено в памяти?


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 23, 2012, 15:51
В какой стандартный хеш у Вас? Напр если QHash <const char *, int>, то это может оказаться совсем не то (сравниваются адреса а не содержимое)


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 16:02
В какой стандартный хеш у Вас? Напр если QHash <const char *, int>, то это может оказаться совсем не то (сравниваются адреса а не содержимое)
Я просто изучаю исходники QHash и вижу, что там при одном условии может использоваться выравнивание в памяти для Node, где Node - это структура, являющаяся элементом контейнера и содержащая ключ, хэш ключа и значение элемента.

Однако функция выравнивания qMallocAligned() вызывается только если __alignof__(Node) возвратит число, большее 8 байт. Глянув на свой класс Node (он аналогичен), я получил размер в 4 байта. Другими словами, выравнивание в памяти - это не то, что я ищу. Вот ведь вешалка... :(


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 16:09
В аттач проект хэша присоединил. Алгоритм вычисления хэша ключа взят у QHash. Поиск по ключу выполняется в 3 раза медленнее, нежели чем у QHash.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 23, 2012, 16:29
Вот я и спрашиваю с каким стандартным QHash Вы сравниваете. Если QHash <const char *, ...>, то он сравнивает адрес строки, что быстрее хотя и не то что нужно. Вычисление ключа я бы сделал так

Код
C++ (Qt)
uint32 hash( const char * str )
{
if (!str) return 0;
uint32 val = 0;
char * dst = (char *) &val;
size_t i = 0;
while (*str) {
 dst[i] ^= *str;
 ++str;
 i = (i + 1) % sizeof(val);
}
return val;
}


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 16:34
Во я и спрашиваю с каким стандартным QHash Вы сравниваете. Если QHash <const char *, ...>, то он сравнивает адрес строки, что быстрее хотя и не то что нужно.
Стоп. Да, именно с QHash<const char *, ...>. То есть он не вычисляет хэш для "const char *"?! Едрить :(

Вычисление ключа я бы сделал так

Код
C++ (Qt)
uint32 hash( const char * str )
{
if (!str) return 0;
uint32 val = 0;
char * dst = (char *) &val;
size_t i = 0;
while (*str) {
 dst[i] ^= *str;
 ++str;
 i = (i + 1) % sizeof(val);
}
return val;
}
Я код вычисления хэша взял из самого QHash, чтобы уж совсем одинаково было. Так-то да, его хэш не самый быстрый.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 16:36
Вот я и спрашиваю с каким стандартным QHash Вы сравниваете. Если QHash <const char *, ...>, то он сравнивает адрес строки, что быстрее хотя и не то что нужно.
...
А почему "не то что нужно"?

А вообще, сравнение адреса строки вместо хэша - это корректно? Я не пойму, как это так...


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 16:44
Смотрю в исходниках QHash функцию value() - та, которая получает значение из контейнера по ключу. Она вызывает findNode(), в которой производится безусловное вычисление хэша ключа. То есть вычисление для const char* в QHash выполняется.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 23, 2012, 16:59
Смотрю в исходниках QHash функцию value() - та, которая получает значение из контейнера по ключу. Она вызывает findNode(), в которой производится безусловное вычисление хэша ключа. То есть вычисление для const char* в QHash выполняется.
Да, так оно просто адрес подставляет как ключ
Код
C++ (Qt)
const char * str = "abcdefghi";
 
QHash <const char *, int> hash;
hash[str] = 1;
 
char buf[256];
strcpy(buf, str);
qDebug() << hash.contains(buf);
 


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 17:10
Я просто изучаю исходники QHash и вижу, что там при одном условии может использоваться выравнивание в памяти для Node, где Node - это структура, являющаяся элементом контейнера и содержащая ключ, хэш ключа и значение элемента.

Однако функция выравнивания qMallocAligned() вызывается только если __alignof__(Node) возвратит число, большее 8 байт. Глянув на свой класс Node (он аналогичен), я получил размер в 4 байта. Другими словами, выравнивание в памяти - это не то, что я ищу. Вот ведь вешалка... :(
Походу я не прав. Для того чтобы определить вызывать qMallocAligned() или qMalloc() в QHash используется конструкция QMAX(sizeof(Node), __alignof__(Node)). А sizeof для моего класса элемента выдаёт 16. А поскольку мой Node аналогичен Node QHash'а, то можно предположить, что выравнивание в памяти элементов в QHash производится.

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

Из оставшегося...

Предварительное резервирование элементов: QHash выделяет заведомо больше памяти, чем реально нужно. Влияет ли это как-то на lookup? Вряд ли.

Выравнивание в памяти: а вот здесь наверное может быть. Но как так - тоже непонятно...

Других различий у QHash и приведённого мною кода вроде нет.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 23, 2012, 17:14
Очевидно, что сам код вычисления хэша ключа не является той причиной, что приводит к различной скорости lookup'а у моего хэша и QHash, т.к. алгоритм один и тот же.
По-моему как раз является, т.к. ключ = адрес и прогон по байтам - 2 большие разницы


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 17:20
Да, так оно просто адрес подставляет как ключ
Код
C++ (Qt)
const char * str = "abcdefghi";
 
QHash <const char *, int> hash;
hash[str] = 1;
 
char buf[256];
strcpy(buf, str);
qDebug() << hash.contains(buf);
 

Непонятно. Вот QHash:
Код
C++ (Qt)
template <class Key, class T>
Q_INLINE_TEMPLATE bool QHash<Key, T>::contains(const Key &akey) const
{
   return *findNode(akey) != e;
}
 
template <class Key, class T>
Q_OUTOFLINE_TEMPLATE typename QHash<Key, T>::Node **QHash<Key, T>::findNode(const Key &akey,
                                                                           uint *ahp) const
{
   Node **node;
   uint h = qHash(akey); //Здесь безусловное вычисление ключа.
   //Но как это согласуется с приведённым Вами кодом?..
   //Тогда бы он "false" не показывал
 
   if (d->numBuckets) {
       node = reinterpret_cast<Node **>(&d->buckets[h % d->numBuckets]);
       Q_ASSERT(*node == e || (*node)->next);
       while (*node != e && !(*node)->same_key(h, akey))
           node = &(*node)->next;
   } else {
       node = const_cast<Node **>(reinterpret_cast<const Node * const *>(&e));
   }
   if (ahp)
       *ahp = h;
   return node;
}


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 17:25
По-моему как раз является, т.к. ключ = адрес и прогон по байтам - 2 большие разницы
Это я понимаю, и тогда, с учётом этого момента, я получу у себя такую же скорость, но хотел бы уж до конца разобраться. В исходниках QHash вижу одно, а результат в приложении с QHash - другой.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 23, 2012, 17:28
Код
C++ (Qt)
   uint h = qHash(akey); //Здесь безусловное вычисление ключа.
   //Но как это согласуется с приведённым Вами кодом?..
   //Тогда бы он "false" не показывал
}
Так адрес buf другой (это содержимое == str). Он просто использует адрес (как void *). Насколько я понимаю Вас это не устроит


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 17:35
Так адрес buf другой (это содержимое == str).
Да, я это понимаю.

Он просто использует адрес (как void *).
Хотел найти в коде QHash, как у него это выходит - сравнение по адресу, а не по хэшу, если в исходниках стоит вызов qHash() без всяких условий. Может у меня исходники левые ??? Уже не знаю, что и думать...

Насколько я понимаю Вас это не устроит
А насколько это может быть чревато? Скорость очень заманчива. Самый быстрый алгоритм вычисления хэша не даст такой скорости как простое сравнение адресов. И я собираюсь использовать именно const char* в виде ключей.


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 23, 2012, 17:59
Кого-то из нас переклинило на простой вещи  :)
Хотел найти в коде QHash, как у него это выходит - сравнение по адресу, а не по хэшу, если в исходниках стоит вызов qHash() без всяких условий. Может у меня исходники левые ??? Уже не знаю, что и думать...
Просто идите по шагам (step in) и увидите что он вызывает qHash (просто ф-ция без класса) которая присваивает ключу адрес

А насколько это может быть чревато? Скорость очень заманчива. Самый быстрый алгоритм вычисления хэша не даст такой скорости как простое сравнение адресов. И я собираюсь использовать именно const char* в виде ключей.
Для строки которую надо найти - где же Вы возьмете тот самый адрес что сидит в ключе хеша? У Вас только содержимое, по нему и придется искать


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 23, 2012, 18:18
Кого-то из нас переклинило на простой вещи  :)
:D

Просто идите по шагам (step in) и увидите что он вызывает qHash (просто ф-ция без класса) которая присваивает ключу адрес
Да, точно! Не подумал это попробовать

Для строки которую надо найти - где же Вы возьмете тот самый адрес что сидит в ключе хеша? У Вас только содержимое, по нему и придется искать
Другими словами QHash с const char* в фабрике я использовать не могу. Так или иначе потребовалось бы использовать какую-нибудь обёртку, вроде QLatin1String, правильно?

Сейчас попробовал QHash с ключом QLatin1String - кошмар! Более 300 мс.

Спасибо большое, Игорь. Очень помогли разобраться :)


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 24, 2012, 13:24
Вычисление ключа я бы сделал так

Код
C++ (Qt)
uint32 hash( const char * str )
{
if (!str) return 0;
uint32 val = 0;
char * dst = (char *) &val;
size_t i = 0;
while (*str) {
 dst[i] ^= *str;
 ++str;
 i = (i + 1) % sizeof(val);
}
return val;
}
Интересный вариант. По скорости сравним с murmurhash3 и superfasthash. Отличительная особенность - не требуется вычислять размер строки ключа. superfasthash немного быстрее, но требуется размер (strlen). Даже не знаю, на каком варианте остановиться.

Игорь, подскажите пожалуйста, тестировали ли Вы свой вариант на количество коллизий?


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: Igors от Июль 24, 2012, 13:43
Интересный вариант. По скорости сравним с murmurhash3 и superfasthash. Отличительная особенность - не требуется вычислять размер строки ключа. superfasthash немного быстрее, но требуется размер (strlen). Даже не знаю, на каком варианте остановиться.
Ну да, задумка обойтись без strlen, вероятно то на то и выходит

Коллизии (хеш-ключ одинаков, в корзине 2 или более) не тестировал. Считаю что к ним надо относиться философски - ну будет так будет, в конце-концов "nothing is perfect". Можно и автоподстройку сделать - давать различные  QHash::reserve(N) и считать сколько с одинаковым хеш-ключом. Выполняется 1 раз на старте


Название: Re: Ассоциативный контейнер для быстрого lookup'а
Отправлено: alexis031182 от Июль 24, 2012, 13:51
Ну да, задумка обойтись без strlen, вероятно то на то и выходит
Мне Ваш вариант очень импонирует своей лаконичностью. Кратко, удобно: всё необходимое сразу из строки.

Коллизии (хеш-ключ одинаков, в корзине 2 или более) не тестировал. Считаю что к ним надо относиться философски - ну будет так будет, в конце-концов "nothing is perfect".
Согласен. Они (коллизии) есть у всех без исключения хэшей.

Можно и автоподстройку сделать - давать различные QHash::reserve(N) и считать сколько с одинаковым хеш-ключом. Выполняется 1 раз на старте
Дельно. Спасибо. Впрочем, с моими ключами, известными заранее, коллизий нет.