Re[2]: Когда linear search быстрее hash map
От: c-smile Канада http://terrainformatica.com
Дата: 16.10.17 15:41
Оценка:
Здравствуйте, Engler, Вы писали:

E>Здравствуйте, c-smile, Вы писали:


E>Если честно, я бы присмотрелся к Вашему тесту более внимательно.


E>1. Например, настораживает передача строки по значению.

E>
E>FormatterType t2e(std::string s) { ... }
E>


Не важно. Ибо эта сигнатура используется во всех сравниваемых функциях.

E>2. В контейнерах, std::map / std::unordered_map вы храните объекты std::string,

E>в то время когда в функция if_t2e сравнение идет с использованием обертки slice.

А это причем?

E>3. Дальше, маленькие объемы + еще std::string тот случай, когда нужно быть особенно внимательным,

E>поэтому еще нужно посмотреть как компилятор работает со строками ( какие там оптимизации ),
E>вполне возможно что разные компиляторы дадут разный результат.

E>4. Различные реализации std::hash / std::equal_to / std::less для std::string могут дать разные результат.

E>5. Можно попробовать хранить const char* вместо std::string, возможно тоже покажет другой результат.

Я про это и говорю. Надо смотреть на реальную ситуацию, а не тупо брать std::map для поиска в наборе.
Re[3]: Когда linear search быстрее hash map
От: MTD https://github.com/mtrempoltsev
Дата: 16.10.17 16:10
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Я про это тоже написал. Это примерно то что генерирует perfect hash (gperf).


Ну генерирует он сильно другое (насколько я помню), кроме того, при чем в контексте обсуждения он (это же генератор хеш-функции)?
Re[4]: Когда linear search быстрее hash map
От: c-smile Канада http://terrainformatica.com
Дата: 16.10.17 16:51
Оценка:
Здравствуйте, MTD, Вы писали:

MTD>Здравствуйте, c-smile, Вы писали:


CS>>Я про это тоже написал. Это примерно то что генерирует perfect hash (gperf).


MTD>Ну генерирует он сильно другое (насколько я помню), кроме того, при чем в контексте обсуждения он (это же генератор хеш-функции)?


gperf генерирует функцию в которой strcmp используется ровно один раз (т.е. полный проход строки). В качестве дискриминатора используется длина строки и один или несколько значений characters. Примерно тот же FSA что и ты написал как я понял.
Re[2]: Когда linear search быстрее hash map
От: c-smile Канада http://terrainformatica.com
Дата: 16.10.17 16:59
Оценка:
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, c-smile, Вы писали:


CS>>Приводится случай когда O(N) lookup кроет O(1) как бык овцу. А над O(log N) — вообще сплошное надругательство.


R>Так ведь никто и не обещал, что O(1) ВСЕГДА будет быстрее O(log N), а O(log N) ВСЕГДА быстрее O(N). Потому, что все эти оценки задают лишь асимптотику зависимости времени выполнения от размера входной последовательности, но не являются точной зависимостью. Они позволяют лишь говорить о том, что существует такой размер входной последовательности, свыше которого O(1) начинает выигрывать у O(log N), а O(log N) у O(N). А вот каким будет этот размер, зависит уже от...


Ну собственно об этом и речь. Это все про эту вот цитату:

Практик: "Если он вызывается не один раз, то очевидно что std::map будет эффективнее, т.к. инициализация дерева будет происходить только один раз. И это кстати не теория — я использую на практике именно такой подход (только со string_view, а не string в качестве ключа для map)."
Re[2]: Когда linear search быстрее hash map
От: c-smile Канада http://terrainformatica.com
Дата: 16.10.17 17:01
Оценка:
Здравствуйте, uzhas, Вы писали:

U>Здравствуйте, c-smile, Вы писали:


CS>>Эта вот дискуссия
Автор: c-smile
Дата: 05.10.17
подвигла мя на написание статьи на тему.

U>ты, к сожалению, не понял, что та дискуссия совсем не производительность, а про качество кода
U>тебе даже форматтер кода намекнул об этом

Что есть такое "качество кода" в обсуждаемом контексте?
Re[2]: Когда linear search быстрее hash map
От: c-smile Канада http://terrainformatica.com
Дата: 16.10.17 18:29
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, c-smile, Вы писали:


CS>>Приводится случай когда O(N) lookup кроет O(1) как бык овцу.


N>В полтора раза (3.44 против 2.13) это "как бык овцу"?

N>"Ну извините..." ((с) Вовочка)
N>И заметим, всего-то при 9 записях линейный поиск уже хуже мапы. А что дальше?

CS>> А над O(log N) — вообще сплошное надругательство.


N>Аж на пару десятков процентов.


Ну дык, тут копеечка, там копеечка...


N>А если тут нужен действительно быстрый поиск — то как это делать — смотреть, например, в Kamailio:


N>
N>#define _acce_ 0x65636361   /* "acce" */
N>#define _allo_ 0x6f6c6c61   /* "allo" */
N>#define _auth_ 0x68747561   /* "auth" */
N>#define _oriz_ 0x7a69726f   /* "oriz" */
N>#define _atio_ 0x6f697461   /* "atio" */
N>#define _call_ 0x6c6c6163   /* "call" */
N>#define __id2_ 0x2064692d   /* "-id " */
N>#define __id1_ 0x3a64692d   /* "-id:" */

N>[...]

N>#define FIRST_QUATERNIONS       \
N>        case _via1_: via1_CASE; \
N>        case _from_: from_CASE; \
N>        case _to12_: to12_CASE; \
N>        case _cseq_: cseq_CASE; \
N>        case _call_: call_CASE; \
N>        case _cont_: cont_CASE; \
N>        case _rout_: rout_CASE; \
N>        case _max__: max_CASE;  \
N>


N>ну и так далее — строка нарезается стандартными порциями по 4 байта, переводится к нижнему регистру (SIP заголовки — case-insensitive) и затем задача компилятора — выбрать правильный вариант разложить case (обычно он строит что-то вроде дерева). Да, это очень жёсткая целевая оптимизация. Но там она оправдана.


Да вот тоже не факт на самом деле ...

1) endianness 2) перевод в нижний регистр это O(n) и 3) switch на разреженных case values это всё тот же linear lookup.
Немного лучше, но тем не менее нужно смотреть на конкретное распределение tokens probability. Про которые компилятор не знает ничего и может сгенерировать плохой lookup.
Re[2]: Когда linear search быстрее hash map
От: prog123 Европа  
Дата: 16.10.17 18:44
Оценка:
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, c-smile, Вы писали:


CS>>Приводится случай когда O(N) lookup кроет O(1) как бык овцу. А над O(log N) — вообще сплошное надругательство.


R>Так ведь никто и не обещал, что O(1) ВСЕГДА будет быстрее O(log N), а O(log N) ВСЕГДА быстрее O(N). Потому, что все эти оценки задают лишь асимптотику зависимости времени выполнения от размера входной последовательности, но не являются точной зависимостью. Они позволяют лишь говорить о том, что существует такой размер входной последовательности, свыше которого O(1) начинает выигрывать у O(log N), а O(log N) у O(N). А вот каким будет этот размер, зависит уже от...


В этих формулах еще есть константа, про которую забывают, которая и есть все эти хэши, кэши, предсказания ветвлений, прифечинг и прочее. При большом n ее влияние не значительно, поэтому в формулах ее и отбрасывают, а при малых n она как раз может оказать решающее влияние на скорость.
Re[3]: Когда linear search быстрее hash map
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 16.10.17 19:34
Оценка:
Здравствуйте, c-smile, Вы писали:

N>>Аж на пару десятков процентов.

CS>Ну дык, тут копеечка, там копеечка...

Ну если уверены, что эта копеечка на что-то повлияет — ok, делать так. Оставив под, условно говоря, `#if 0` простой вариант.

N>>ну и так далее — строка нарезается стандартными порциями по 4 байта, переводится к нижнему регистру (SIP заголовки — case-insensitive) и затем задача компилятора — выбрать правильный вариант разложить case (обычно он строит что-то вроде дерева). Да, это очень жёсткая целевая оптимизация. Но там она оправдана.


CS>Да вот тоже не факт на самом деле ...

CS>1) endianness

Решено основным случаем (для BE разворачивают порядок).

CS> 2) перевод в нижний регистр это O(n)


Он делается через or с 0x20202020 (или чуть меньшей маской, где не буква).

CS> и 3) switch на разреженных case values это всё тот же linear lookup.


Компиляторы часто строят switch как дерево ifʼов.

CS>Немного лучше, но тем не менее нужно смотреть на конкретное распределение tokens probability. Про которые компилятор не знает ничего и может сгенерировать плохой lookup.


Оно для этой задачи, грубо говоря, равномерно — вероятность встретить поле заголовка слабо зависит от букв начала его имени.
(Хуже то, что на нём любые branch prediction лажаются.)
The God is real, unless declared integer.
Re: Когда linear search быстрее hash map
От: Pzz Россия https://github.com/alexpevzner
Дата: 16.10.17 19:47
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Не ходите дети в Африку гулять используйте std::map не по назначению.


А если бы ты в своем линейном поиске свел проверку к memcmp(), он победил бы во всех случаях.

При использовании perfect hash в реальной жизни было бы не лишне сделать в конце проверку, что мы нашли именно то, что искали. На заданном наборе он, конечно, не дает коллизий, но кто сказал, что его с гарантией накормят одной из валидных строк?
Re: Когда linear search быстрее hash map
От: prog123 Европа  
Дата: 16.10.17 20:47
Оценка:
Здравствуйте, c-smile, Вы писали:



CS>Эта вот дискуссия
Автор: c-smile
Дата: 05.10.17
подвигла мя на написание статьи на тему.


CS>Приводится случай когда O(N) lookup кроет O(1) как бык овцу. А над O(log N) — вообще сплошное надругательство.


CS>Не ходите дети в Африку гулять используйте std::map не по назначению.


а можешь такой вариант проверить? идея в том чтобы положить все данные в массив и последовательно, так чтобы хорошо работал кэш, сравнивать куски памяти с помощью memcmp.
#include<memory.h>
#include<string.h>
#include<string>
#include<iostream>

//enum FormatterType {
//    simple_formatter,        0
//    integer_formatter,        1
//    decimal_formatter,        2
//    currency_formatter,        3
//    date_formatter,            4
//    date_local_formatter,    5
//    time_formatter,            6
//    time_local_formatter,    7
//    enum_formatter,            8
//    duration_formatter        9
//};

int str2enum(std::string const& s)
{    
    static int const ssize = strlen("          ");
    static char  m[] =        
        "          "
        "text      "
        "integer   "
        "decimal   "
        "currency  "
        "date      "
        "date-local"
        "time      "
        "time-local"
        "enum      "
        "duration  "
        ;
    static int const msize = strlen(m) / ssize;

    if (s.size() > ssize)
        return 0; // 0-simple_formatter

    memset(m, ' ', ssize);
    memcpy(m, s.c_str(), s.size());

    for (int i = 1; i < msize; ++i)
    {
        if (memcmp(m, m + i * ssize, ssize) == 0)
            return i - 1;
    }
    
    return 0; // 0-simple_formatter
}

int main(int , char**)
{
    std::cout << str2enum("text") << std::endl;
    std::cout << str2enum("date-local") << std::endl;
    std::cout << str2enum("duration") << std::endl;
    std::cout << str2enum("abc") << std::endl;
    std::cout << str2enum("abcdefjhiklmnopst") << std::endl;

    return 0;
}
Re[2]: Когда linear search быстрее hash map
От: c-smile Канада http://terrainformatica.com
Дата: 16.10.17 21:11
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Здравствуйте, c-smile, Вы писали:


CS>>Не ходите дети в Африку гулять используйте std::map не по назначению.


Pzz>А если бы ты в своем линейном поиске свел проверку к memcmp(), он победил бы во всех случаях.


А как это помогает?

Pzz>При использовании perfect hash в реальной жизни было бы не лишне сделать в конце проверку, что мы нашли именно то, что искали. На заданном наборе он, конечно, не дает коллизий, но кто сказал, что его с гарантией накормят одной из валидных строк?


Ну дык оно же там есть:

// gperf approach +++

FormatterType gperf_t2e(std::string name)
{
  const struct enum_def * ed = enum_names::find_def(name.c_str(), (unsigned int)name.size());
  return ed ? FormatterType(ed->value) : simple_formatter;
}
// gperf approach ---


Gperf возвращает nullptr если не найден. И потом входной поток содержит токены не из набора.
Re[3]: Когда linear search быстрее hash map
От: Pzz Россия https://github.com/alexpevzner
Дата: 16.10.17 21:22
Оценка:
Здравствуйте, c-smile, Вы писали:

Pzz>>А если бы ты в своем линейном поиске свел проверку к memcmp(), он победил бы во всех случаях.


CS>А как это помогает?


Быстрее работает, чем сравнивать строки в цикле побайтово.
Re[2]: Когда linear search быстрее hash map
От: Engler Беларусь  
Дата: 16.10.17 21:32
Оценка:
Здравствуйте, Engler, Вы писали:

E>Здравствуйте, c-smile, Вы писали:


E>Если честно, я бы присмотрелся к Вашему тесту более внимательно.

Посмотрел немного более внимательно.
Мне все-таки кажется, что сравнение в тесте не совсем корректное.
Функция,
FormatterType if_t2e(std::string name)

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


И это, не совсем честный линейный поиск. Вместо этого, по хорошему надо делать поиск в std::vector.
Тогда получится, что вы сравниваете похожие вещи.
  Код
std::vector<std::pair<std::string, FormatterType>> vector_map = {
    std::pair<std::string,FormatterType> { "text", simple_formatter },
    std::pair<std::string,FormatterType> { "integer", integer_formatter },
    std::pair<std::string,FormatterType> { "decimal", decimal_formatter },
    std::pair<std::string,FormatterType> { "currency", currency_formatter },
#if ENTRIES > 4
    std::pair<std::string,FormatterType> { "date", date_formatter },
#if ENTRIES > 5
    std::pair<std::string,FormatterType> { "date-local", date_local_formatter },
#if ENTRIES > 6
    std::pair<std::string,FormatterType> { "time", time_formatter },
#if ENTRIES > 7
    std::pair<std::string,FormatterType> { "time-local", time_local_formatter },
#if ENTRIES > 8
    std::pair<std::string,FormatterType> { "enum", enum_formatter },
#if ENTRIES > 9
    std::pair<std::string,FormatterType> { "duration", duration_formatter }
#endif
#endif
#endif
#endif
#endif
#endif
};

FormatterType vector_search(const std::string& name)
{
    auto it = std::find_if(vector_map.begin(), vector_map.end(),
        [&name](const std::pair<std::string, FormatterType>& p) { return name == p.first; });
        
    return (it != vector_map.end()) ? it->second : simple_formatter;
}

// это в main
run_test(vector_search, "vector_search");


Вот что у меня получилось,

  Результаты

map_t2e 6.85883 359490000
unordered_map_chars_t2e 4.56228 359490000
unordered_map_string_index 5.755 359490000
if_t2e 2.12067 359490000
gperf_t2e 2.27001 359490000
vector_search 6.77989 359490000



А вот кстати, поведение std::unordered_map::find меня удивило.
Она вызывает lower_bound, которая в свою очередь уже
1) строит хеш [вызывается кастомный функтор], что в общем-то естесвенно
2) вызывает опреатор == ( для поиска элеметна в листе , на который указывает bucket )
3) вызывает опреатор == еще раз ( пока не понял зачем ).

В итоге мы имеем хорошо оптимизированную функцию if_t2e,
против непонятно какой реализации unordered_map.

P.S: VS VS2015
Re[4]: Когда linear search быстрее hash map
От: · Великобритания  
Дата: 16.10.17 22:56
Оценка:
Здравствуйте, MTD, Вы писали:

MTD> Для оценки скорости алгоритмов предлагается использовать его асиптотику — это не всегда верно. Например, у бинарного дерева и упорядоченного массива асимптотика поиска

Какая-то фигня.
Бинарное дерево можно реализовать in-place в массиве. А упорядоченный массив может быть массивом указателей с кеш-промахами. И внезапно дерево заработает быстрее упорядоченного массива.
Просто надо учитывать конкретные реализации структур данных — чем именно занимается процессор, а не оперировать абстрактными типами. Тогда не придётся "слепо верить", а просто тупо считать.

По сабжу: мне кажется самым эффективным и универсальным будет std::vector<std::pair<char[MAX_KEY_SIZE], FormatterType>> отсортированный по pair.first и искать в нём используя binary_search (кстати, там имхо реализована оптимизация для малых размеров).
avalon/2.0.6
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: Когда linear search быстрее hash map
От: alex_public  
Дата: 17.10.17 00:09
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Эта вот дискуссия
Автор: c-smile
Дата: 05.10.17
подвигла мя на написание статьи на тему.

CS>Приводится случай когда O(N) lookup кроет O(1) как бык овцу. А над O(log N) — вообще сплошное надругательство.
CS>Не ходите дети в Африку гулять используйте std::map не по назначению.

Хм, я тебе вроде как всё понятно разъяснил в этом http://rsdn.org/forum/cpp/6934121.1
Автор: alex_public
Дата: 15.10.17
сообщение той темки. Непонятно зачем ты завёл новую. Разве что хотелось ещё поговорить об этом, но нечего было ответить на то сообщение? )))
Re[4]: Когда linear search быстрее hash map
От: c-smile Канада http://terrainformatica.com
Дата: 17.10.17 01:57
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Здравствуйте, c-smile, Вы писали:


Pzz>>>А если бы ты в своем линейном поиске свел проверку к memcmp(), он победил бы во всех случаях.


CS>>А как это помогает?


Pzz>Быстрее работает, чем сравнивать строки в цикле побайтово.


Нет, не быстрее:

Это вот 2.82 секунды

bool operator == ( const slice& r ) const
      {
        if( length != r.length )
          return false;
        for( unsigned int i = 0; i < length; ++i )
          if( start[i] != r.start[i] )
            return false;
        return true;
      }


А это

bool operator == ( const slice& r ) const
      {
        if( length != r.length )
          return false;
        return memcmp(start, r.start, length) == 0;
      }


3.06 секунды. Затраты на вызов функции и подготовка поиска в memcmp.
memcmp выигрывает на больших последовательностях, но не в этом случае.
Re[3]: Когда linear search быстрее hash map
От: c-smile Канада http://terrainformatica.com
Дата: 17.10.17 02:05
Оценка:
Здравствуйте, Engler, Вы писали:

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


E>В итоге мы имеем хорошо оптимизированную функцию if_t2e,


Что там оптимизировано-то?

Банальный же if-elif-elif-else... т.е. последовательный (линейный) перебор.
Re[4]: Когда linear search быстрее hash map
От: elmal  
Дата: 17.10.17 05:01
Оценка:
Здравствуйте, MTD, Вы писали:

MTD>Речь не только про малые n.

Ух ты. Хотелось бы посмотреть, как линейный поиск обойдет лучшие асимптотически алгоритмы при n хотя б в миллион. А лучше в миллиард.

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

Кеш промахи и все такое имеют значение только для малых n. Но даже без учетов архитектуры современных процессоров, алгоритмы в лоб чаще проще и в результате за 1 проход выполняется меньшее количество команд. Соответственно да, для малых n простой алгоритм может быть быстрее и даже скорее всего будет. Но с ростом n всегда получится дерьмо. Учетом архитектуры процессора и тому подобного на больших n ты максимум поднимешь производительность в 10 раз. Ладно, даже в 100. Ладно, даже в тысячу, хоть это и маловероятно. Вот только смена асимптотики — это ускорение может быть в миллионы и миллиарды раз. Главное чтоб памяти хватило чтоб это n вместить.
Re[5]: Когда linear search быстрее hash map
От: MTD https://github.com/mtrempoltsev
Дата: 17.10.17 05:33
Оценка:
Здравствуйте, elmal, Вы писали:

E>Ух ты.


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

E>Кеш промахи и все такое имеют значение только для малых n.


Да что ты говоришь. Вот у тебя есть 100 терабайт данных — это малое n? Теперь представь, что у тебя есть оперативная память (кеш) и дисковый массив. Тебе все еще не очевидно, что количество промахов надо минимизировать?

E>Вот только смена асимптотики — это ускорение может быть в миллионы и миллиарды раз.


Я где-то это отрицал? Ткни пальцем.
Re[5]: Когда linear search быстрее hash map
От: MTD https://github.com/mtrempoltsev
Дата: 17.10.17 05:40
Оценка:
Здравствуйте, ·, Вы писали:

·>Какая-то фигня.


Да, так обычно получается, если не прочитать, но начать отвечать.

·>Бинарное дерево можно реализовать in-place в массиве. А упорядоченный массив может быть массивом указателей с кеш-промахами. И внезапно дерево заработает быстрее упорядоченного массива.


Молодец. Любопытно, что побудило тебя написать это?

·>Просто надо учитывать конкретные реализации структур данных — чем именно занимается процессор, а не оперировать абстрактными типами.


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

·>По сабжу: мне кажется самым эффективным и универсальным будет std::vector<std::pair<char[MAX_KEY_SIZE], FormatterType>> отсортированный по pair.first и искать в нём используя binary_search (кстати, там имхо реализована оптимизация для малых размеров).


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