boost unordered中桶个数

来源:互联网 发布:c语言大型程序源代码 编辑:程序博客网 时间:2024/06/05 00:37

本文接着 http://blog.csdn.net/freemannnn/article/details/24546963 继续讨论。

 

桶的初始个数

在boost头文件boost\unordered\detail\util.hpp中定义了桶的初始数目:

static const std::size_t default_bucket_count = 11;

也就是说,一个unordered中,即使没有元素,桶的个数至少也是default_bucket_count。

 

影响桶的个数的因素

首先,桶的个数是根据需要实时变化的。在unordered中元素个数一定的情况下,如果桶的个数过少,就会导致每个桶中的元素个数过多,从而使查找效率低下;如果桶的个数过多,就会导致很多桶都是空的,从而使内存利用率低下。因此,动态的选取一个合适桶的个数很关键。

影响它的因素有:

1. X(size_type n)

Construct an empty container with at least n buckets (X is the container type).

unordered的构造函数,n直接指定了桶的个数。

2. X(InputIterator i, InputIterator j, size_type n)

Construct an empty container with at least n buckets and insert elements from the range [i, j) (X is the container type).

unordered的构造函数,n直接指定了桶的个数。

3. void rehash(size_type n)

Changes the number of buckets so that there at least n

显式调用rehash函数(很多情况下,此函数被其它函数调用,这种情况称隐式调用),n直接指定了桶的个数。

4. float max_load_factor(float z)

Changes the container's maximum load factor, using z as a hint

改变最大负载因子(请注意仅仅是提示,如果最大负载因子z不太合理,即过大或过小,可能导致设置失败)。max_load_factor默认值为1.0boost\unordered\detail\table.hpp文件中table类的构造函数中初始化了此值),可以由用户设置。

unordered内部维护一个不变式:load_factor <= max_load_factor

负载因子load_factor可以通过调用float load_factor() const获取,它的定义是The average number of elements per bucket,即unordered中元素个数 / unordered中桶的个数。过大的负载因子会导致查找速度变慢,过小的负载因子会导致内存空间的严重浪费。

如果改变max_load_factor导致上述不变式不成立(load_factor > max_load_factor),这时候unordered内部就会自动调用rehash以增加桶的个数,从而使load_factor减小,最终保证不变式成立。

5. 其他导致unordered中元素个数变化函数(inserterase等操作)

unordered中元素个数变化,导致上述不变式不成立,以后操作同情形4

 

桶的个数是如何变化的。

当桶的个数m发生变化后,原来的哈希表就失效了。

举个例子:

原来桶的个数m为11, 哈希值为25的元素,存放在第3号桶中。

现在桶的个数m变为19,哈希值为25的元素,存放在第6号桶中。

所以当桶的个数发生变化后,整个哈希表就要重新构造了。这个代价是非常大的。为此,unordered内部实现采取了措施以避免桶的个数发生变化的频率过高。使用的方法和vector一样,每次分配足够多的桶。当上次分配的桶不够用了后,下次分配更多的桶。以下是桶分配的个数的列表(这个列表可以在boost\unordered\detail\util.hpp中找到):

#define BOOST_UNORDERED_PRIMES \

    (17ul)(29ul)(37ul)(53ul)(67ul)(79ul) \

    (97ul)(131ul)(193ul)(257ul)(389ul)(521ul)(769ul) \

    (1031ul)(1543ul)(2053ul)(3079ul)(6151ul)(12289ul)(24593ul) \

    (49157ul)(98317ul)(196613ul)(393241ul)(786433ul) \

    (1572869ul)(3145739ul)(6291469ul)(12582917ul)(25165843ul) \

    (50331653ul)(100663319ul)(201326611ul)(402653189ul)(805306457ul) \

    (1610612741ul)(3221225473ul)(4294967291ul)

http://blog.csdn.net/freemannnn/article/details/24546963         当初始桶个数default_bucket_count=11不够时,就分配17个桶,再不够了,就分配29个桶,以此类推(为什么桶的个数是素数,请参http://blog.csdn.net/freemannnn/article/details/24546963)。 

 

下面更细节的讨论unordered中重新选择桶的个数。

当向unordered中增加若干元素以至于当前load_factor大于max_load_factor时(并不是每次增加元素都会导致load_factor大于max_load_factor),就会重新选择桶的个数。

选择的方法是:

1. 先计算当前最少应该需要的桶个数

min_bucket_count = element_count / max_load_factor

其中element_count是unordered当前元素个数,max_load_factor是最大负载因子。

2. 在BOOST_UNORDERED_PRIMES列表中找到比min_bucket_count大的最小元素(也就是BOOST_UNORDERED_PRIMES列表中比min_bucket_count刚好大一点点的元素,比如min_bucket_count是23,那么找到的元素是29ul),这个元素即是新的桶的个数。这个选择过程由boost\unordered\detail\util.hpp头文件中的next_prime函数实现。


以下是这个函数的实现细节:

template<class T> 

struct prime_list_template

{

        static std::size_t const value[];

        static std::ptrdiff_t const length;

};

 

// 定义了桶个数数组

// BOOST_PP_SEQ_ENUM宏参见 http://blog.csdn.net/freemannnn/article/details/24531547

template<class T>

std::size_t const prime_list_template<T>::value[] = 

{ BOOST_PP_SEQ_ENUM(BOOST_UNORDERED_PRIMES) };

 

// 定义了桶个数数组元素的个数

// BOOST_PP_SEQ_SIZE 宏参见 http://blog.csdn.net/freemannnn/article/details/24528959

template<class T>

std::ptrdiff_t const prime_list_template<T>::length = 

BOOST_PP_SEQ_SIZE(BOOST_UNORDERED_PRIMES);

 

// 为什么用prime_list_template模板,然后实例化一个prime_list,可能是不想将prime_list_template的value和length成员放在源文件中(非模板类的静态数组成员的定义必须放在源文件中,模板类有这个特权)

typedef prime_list_template<std::size_t> prime_list;

 

// 以下是二分查找的过程

inline std::size_t next_prime(std::size_t num) 

{

        std::size_t constconst prime_list_begin = prime_list::value;

        std::size_t constconst prime_list_end = prime_list_begin + prime_list::length;

        std::size_t const* bound = std::lower_bound(prime_list_begin, prime_list_end, num);

        if(bound == prime_list_end)

            bound--;

        return *bound;

}

0 0