2016-05-03 2 views
5

Я реализовал это решение для получения значение хеш-функции из vector<T>:Быстрая хэш-функция для `станд :: VECTOR`

namespace std 
{ 
    template<typename T> 
    struct hash<vector<T>> 
    { 
     typedef vector<T> argument_type; 
     typedef std::size_t result_type; 
     result_type operator()(argument_type const& in) const 
     { 
      size_t size = in.size(); 
      size_t seed = 0; 
      for (size_t i = 0; i < size; i++) 
       //Combine the hash of the current vector with the hashes of the previous ones 
       hash_combine(seed, in[i]); 
      return seed; 
     } 
    }; 
} 

//using boost::hash_combine 
template <class T> 
inline void hash_combine(std::size_t& seed, T const& v) 
{ 
    seed ^= std::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 
} 

Но это решение не масштабируется вообще: с vector<double> из 10 миллионов элементов это потребуется больше 2,5 с (согласно VS).

Существует ли быстрая хэш-функция для этого сценария?

Обратите внимание, что создание хэш-значения из вектора ссылки не является допустимым решением, так как соответствующий unordred_map будет использоваться в различных трасс и, кроме того два vector<double> с тем же содержимым, но с разными адресами будут отображаться по-другому (нежелательного поведения для это приложение).

+1

Действительно ли вы используете векторы 'double' с 10 миллионами элементов в качестве ключей? Я как-то сомневаюсь в этом. – milleniumbug

+0

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

+0

@milleniumbug это вполне возможно в сценарии с высокой нагрузкой с большим количеством ОЗУ. Возможно, он использует неправильный контейнер или должен смотреть в не-STL-решения. – strangeqargo

ответ

8

Примечание:Asperthecomments, вы получите 25-50x ускорение путем компиляции с оптимизацией. Сделайте это, во-первых. Затем, если он все еще слишком медленный, см. Ниже.


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

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

+0

Итак, теперь с активированной конфигурацией релиза требуется «всего» 117 мс. Но если нам нужно сделать повторный хэширование миллионов ключей (например, из-за плохого коэффициента загрузки в 'unordered_map'), это все еще неприемлемое решение. Поэтому я думаю, что я попытаюсь улучшить его еще больше с помощью вашего параллельного решения! Благодаря! – justHelloWorld

4

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

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

Второе, что вы должны сделать, это не специализируются std::hash на всех vector с, это может сделать вашу программу плохо формируется (как это было предложено в разрешении дефекта, состояние которого я не помню), и по крайней мере является плохой план (поскольку std обязательно позволит себе добавить хеш-комбинацию и хэширование векторов).

Когда я пишу пользовательский хэш, я обычно использую ADL (Koenig Lookup), чтобы упростить его расширение.

namespace my_utils { 
    namespace hash_impl { 
    namespace details { 
     namespace adl { 
     template<class T> 
     std::size_t hash(T const& t) { 
      return std::hash<T>{}(t); 
     } 
     } 
     template<class T> 
     std::size_t hasher(T const& t) { 
     using adl::hash; 
     return hash(t); 
     } 
    } 
    struct hash_tag {}; 
    template<class T> 
    std::size_t hash(hash_tag, T const& t) { 
     return details::hasher(t); 
    } 
    template<class T> 
    std::size_t hash_combine(hash_tag, std::size_t seed, T const& t) { 
     seed ^= hash(t) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 
    } 
    template<class Container> 
    std::size_t fash_hash_random_container(hash_tag, Container const& c) { 
     std::size_t size = c.size(); 
     std::size_t stride = 1 + size/10; 
     std::size_t r = hash(hash_tag{}, size); 
     for(std::size_t i = 0; i < size; i += stride) { 
     r = hash_combine(hash_tag{}, r, c.data()[i]) 
     } 
     return r; 
    } 
    // std specializations go here: 
    template<class T, class A> 
    std::size_t hash(hash_tag, std::vector<T,A> const& v) { 
     return fash_hash_random_container(hash_tag{}, v); 
    } 
    template<class T, std::size_t N> 
    std::size_t hash(hash_tag, std::array<T,N> const& a) { 
     return fash_hash_random_container(hash_tag{}, a); 
    } 
    // etc 
    } 
    struct my_hasher { 
    template<class T> 
    std::size_t operator()(T const& t)const { 
     return hash_impl::hash(hash_impl::hash_tag{}, t); 
    } 
    }; 
} 

my_hasher - универсальный хешер. Он использует либо хеши, объявленные в my_utils::hash_impl (для std типов), либо бесплатные функции, называемые hash, которые будут хешировать данный тип, чтобы хэш-вещи. В противном случае он пытается использовать std::hash<T>. Если это не удается, вы получаете ошибку времени компиляции.

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

Он понимает векторы и массивы, рекурсивно. Выполнение кортежей и пар требует немного больше работы.

Он проецирует указанные векторы и массивы примерно в 10 раз.

(Примечание:. hash_tag является как-то вроде шутки, и способ, чтобы заставить ADL и предотвратить того, чтобы пересылать-объявить hash специализации в hash_impl пространстве имен, потому что это требование отстой)

Стоимость выборка состоит в том, что вы можете получить больше столкновений.


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

Можно пойти дальше и иметь хаос произвольного доступа (где легко предсказать, что происходит, когда вы редактируете заданное значение хэш-мудрый) и опосредуете весь доступ к вектору. Это сложно.

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

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

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

Смежные вопросы