MSVC std::unordered_map bucket_count()
От: Alexander G Украина  
Дата: 15.12.16 14:34
Оценка:
Заметил, что в MSVC реализации std::unordered_map<int, int>::bucket_count() всегда возвращает степень двойки.
Тогда как другие опробованные реализации (STL из онлайн компиляторов gcc и clang, а также boost) возвращают простое число.

Коллеги, как думаете, такое поведение MSVC — это, скорее, хорошо (битовая операция вместо деления на простое число) или плохо (более вероятны коллизии с отсеченным до индекса букета хешем) ?
Русский военный корабль идёт ко дну!
Re: MSVC std::unordered_map bucket_count()
От: uzhas Ниоткуда  
Дата: 15.12.16 14:48
Оценка: 6 (1) +1 :)
Здравствуйте, Alexander G, Вы писали:


AG>Коллеги, как думаете, такое поведение MSVC — это, скорее, хорошо (битовая операция вместо деления на простое число) или плохо (более вероятны коллизии с отсеченным до индекса букета хешем) ?


можно почитать тут: http://stackoverflow.com/questions/1145217/why-should-hash-functions-use-a-prime-number-modulus
коротко — если хеш функция хорошая, то неважно сколько корзин (то есть степень двойки оптимальна, ибо по модулю быстро считается)
если же хеш функция "не очень", то лучше сверху полирнуть простым модулем
Re: MSVC std::unordered_map bucket_count()
От: uzhas Ниоткуда  
Дата: 15.12.16 14:56
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>а также boost


в бусте несколько политик как раздувать кол-во корзин
см. boost\unordered\detail\buckets.hpp
Re[2]: MSVC std::unordered_map bucket_count()
От: Alexander G Украина  
Дата: 15.12.16 15:14
Оценка:
Здравствуйте, uzhas, Вы писали:

U>в бусте несколько политик как раздувать кол-во корзин


жаль, в интерфейс не протянуто, приходится доверять автопилоту.
но там интересный комментарий:

// While the mix policy is generally faster, the prime policy is a lot
// faster when a large number consecutive integers are used, because
// there are no collisions. Since that is probably quite common, use
// prime policy for integeral types. But not the smaller ones, as they
// don't have enough unique values for this to be an issue.


к сожалению то, что они называют mix policy, активируется только на 64-битном size_t, соответственно, на 32-битных сборках там всегда будет prime policy
Русский военный корабль идёт ко дну!
Re[2]: MSVC std::unordered_map bucket_count()
От: Alexander G Украина  
Дата: 15.12.16 15:53
Оценка:
Здравствуйте, uzhas, Вы писали:

U>можно почитать тут: http://stackoverflow.com/questions/1145217/why-should-hash-functions-use-a-prime-number-modulus

U>коротко — если хеш функция хорошая, то неважно сколько корзин (то есть степень двойки оптимальна, ибо по модулю быстро считается)
U>если же хеш функция "не очень", то лучше сверху полирнуть простым модулем

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

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

С другой стороны, на грабли реализаций в VS 2012 того, что в boost работало хорошо, уже наступал:
[VC2012] проблема с std::this_thread::get_id()
VS 2012 call_once is broken
Вот и сомнения, может и в этом случае лучше старый добрый boost взять
Русский военный корабль идёт ко дну!
Re[3]: MSVC std::unordered_map bucket_count()
От: Кодт Россия  
Дата: 18.12.16 23:08
Оценка: 7 (1)
Здравствуйте, Alexander G, Вы писали:

AG>Как бы ещё узнать, хорошая ли хеш функция.

AG>Допустим, я использую как ключи целые числа, реже строки, ещё реже — хендлы и указатели.
AG>Для всего этого есть стандартная хеш функция.

Возьми набор своих данных и поставь натурный эксперимент: сколько будет коллизий.

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

У меня был такой опыт, когда надо было хранить в таблице пятёрки чисел; но природа этих чисел такова, что первые два почти уникально идентифицировали весь кортеж. Поэтому написал тупую формулу с умножением этих двух чисел на два больших простых числа, взятых наобум. И это ускорило реалтаймовую программу на какую-то ощутимую величину, по сравнению со стандартным хешированием кортежа.
Перекуём баги на фичи!
Re[4]: MSVC std::unordered_map bucket_count()
От: Alexander G Украина  
Дата: 19.12.16 11:26
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Возьми набор своих данных и поставь натурный эксперимент: сколько будет коллизий.


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


Проверил на целочисленных данных. Апроксимированный мой случай выглядит как "тысяча небольших чисел подряд"

Здесь http://rextester.com/l/cpp_online_compiler_visual удобно проверять.
Там можно попробовать и разные компиляторы и буст, и показывается время выполнения (хотя там непонятно, что за конфигурация)

На gcc и clang с 64-битным size_t куча коллизий в boost::, нет их в std:: , но при этом boost несколько быстрее
На vc с 32-битным size_t куча коллизий в std:: и их нет в boost, при этом boost сильно быстрее.
Результаты vc в онлайн компиляторе подозрительные (долго слишком, и разница std и boost слишком большая, возможно, там дебаг).

Проверил студию локально, boost всё же быстрее.
  код
#include <iostream>
#include <boost/unordered_set.hpp>
#include <unordered_set>
#include <map>
#include <functional>
#include <algorithm>
#include <iterator>

volatile size_t c;

int main()
{
    std::cout << "size_t is " << std::numeric_limits<size_t>::digits << " bit\n";
    std::unordered_set<int> v;
    //boost::unordered_set<int> v;
    for (int i = 2000 ; i < 3000 ; i++ )
    {
        v.emplace(i);
    }
    
    for (int times = 0 ; times < 50000 ; times++)
    {
        for (int i = 1000 ; i < 3000 ; i++ )
        {
            c = v.count(i);
        }    
    }


    std::map<int, int, std::greater<int> > collisions;

    for (int bucket = 0, count = v.bucket_count(); bucket < count ; bucket++)
    {
        size_t size = v.bucket_size(bucket);
        if ( size > 1 )
            collisions[size]++;
    }
    
    if ( collisions.empty() )
    {
        std::cout << "No collisions\n";
    }
    else
    {
        for ( auto& it : collisions )
        {
            std::cout << it.first << ":" << it.second << "\n";
        }
    }
    
}


На строках (небольшое количество слов некоторого скриптоязыка) получается коллизий в бусте не меньше, и std чуть лучше при тесте с локальной студией.
Русский военный корабль идёт ко дну!
Отредактировано 19.12.2016 12:23 Alexander G . Предыдущая версия . Еще …
Отредактировано 19.12.2016 11:46 Alexander G . Предыдущая версия .
Re[5]: MSVC std::unordered_map bucket_count()
От: Кодт Россия  
Дата: 19.12.16 16:50
Оценка: 1 (1) +1
Здравствуйте, Alexander G, Вы писали:

AG>На строках (небольшое количество слов некоторого скриптоязыка) получается коллизий в бусте не меньше, и std чуть лучше при тесте с локальной студией.


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

Если имеешь дело со строками, то — какая природа этих строк.
Возможно, что в качестве хеша сойдёт дворд из первых четырёх букв: если твои строки различаются прямо с первой-четвёртой позиции. Считать CRC для длинных строк в этом случае будет избыточно.
Но так же очевидно, что этот хеш очень плохо дружит с политикой "количество корзин — степень 2", так как итоговый хеш (по модулю от количества корзин) будет не двордом, а байтом или вордом от первых 1 или 2 букв соответственно.
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.