diff --git a/include/boost/unordered/detail/fca.hpp b/include/boost/unordered/detail/fca.hpp index 174ff4be..e88faab1 100644 --- a/include/boost/unordered/detail/fca.hpp +++ b/include/boost/unordered/detail/fca.hpp @@ -494,6 +494,14 @@ namespace boost { group_pointer groups; public: + static std::size_t bucket_count_for(std::size_t num_buckets) + { + if (num_buckets == 0) { + return 0; + } + return size_policy::size(size_policy::size_index(num_buckets)); + } + grouped_bucket_array() : empty_value( empty_init_t(), node_allocator_type()), diff --git a/include/boost/unordered/detail/implementation.hpp b/include/boost/unordered/detail/implementation.hpp index 3756a50f..1dd11ecb 100644 --- a/include/boost/unordered/detail/implementation.hpp +++ b/include/boost/unordered/detail/implementation.hpp @@ -2071,8 +2071,6 @@ namespace boost { void recalculate_max_load() { - using namespace std; - // From 6.3.1/13: // Only resize when size >= mlf_ * count std::size_t const bc = buckets_.bucket_count(); @@ -2083,8 +2081,8 @@ namespace boost { // max_load_ = bc == 0 ? 0 - : boost::unordered::detail::double_to_size(floor( - static_cast(mlf_) * static_cast(bc))); + : boost::unordered::detail::double_to_size( + static_cast(mlf_) * static_cast(bc)); } void max_load_factor(float z) @@ -2526,6 +2524,17 @@ namespace boost { new_buckets.insert_node(itnewb, p); } + static std::size_t min_buckets(std::size_t num_elements, float mlf) + { + std::size_t num_buckets = static_cast( + std::ceil(static_cast(num_elements) / mlf)); + + if (num_buckets == 0 && num_elements > 0) { // mlf == inf + num_buckets = 1; + } + return num_buckets; + } + void rehash(std::size_t); void reserve(std::size_t); void reserve_for_insert(std::size_t); @@ -3434,22 +3443,18 @@ namespace boost { template inline void table::rehash(std::size_t num_buckets) { - std::size_t bc = (std::max)(num_buckets, - static_cast(1.0f + static_cast(size_) / mlf_)); + num_buckets = (std::max)( + min_buckets(size_, mlf_), buckets_.bucket_count_for(num_buckets)); - if (bc <= buckets_.bucket_count()) { - return; + if (num_buckets != this->bucket_count()) { + this->rehash_impl(num_buckets); } - - this->rehash_impl(bc); } template inline void table::reserve(std::size_t num_elements) { - std::size_t const num_buckets = static_cast( - std::ceil(static_cast(num_elements) / mlf_)); - + std::size_t num_buckets = min_buckets(num_elements, mlf_); this->rehash(num_buckets); } diff --git a/test/unordered/rehash_tests.cpp b/test/unordered/rehash_tests.cpp index 65c08936..4eb5598b 100644 --- a/test/unordered/rehash_tests.cpp +++ b/test/unordered/rehash_tests.cpp @@ -81,6 +81,300 @@ namespace rehash_tests { BOOST_TEST(postcondition(x, 0)); } + template void rehash_empty_tracking(X*, test::random_generator) + { + // valid for all load factors + float const max_load_factors[] = { + 0.5f, 1.0f, 1e6f, std::numeric_limits::infinity()}; + + std::size_t const max_load_factors_len = + sizeof(max_load_factors) / sizeof(*max_load_factors); + + for (std::size_t i = 0; i < max_load_factors_len; ++i) { + X x; + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(test::detail::tracker.count_allocations, 0u); + + x.max_load_factor(max_load_factors[i]); + + { + BOOST_TEST_EQ(x.bucket_count(), 0u); + + x.rehash(0); + BOOST_TEST_EQ(x.bucket_count(), 0u); + BOOST_TEST_EQ(test::detail::tracker.count_allocations, 0u); + } + + { + BOOST_TEST_EQ(x.bucket_count(), 0u); + + x.rehash(1000); + BOOST_TEST_GE(x.bucket_count(), 1000u); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + x.rehash(0); + BOOST_TEST_EQ(x.bucket_count(), 0u); + BOOST_TEST_EQ(test::detail::tracker.count_allocations, 0u); + } + + { + BOOST_TEST_EQ(x.bucket_count(), 0u); + + x.rehash(1000); + BOOST_TEST_GE(x.bucket_count(), 1000u); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + x.rehash(10); + BOOST_TEST_GE(x.bucket_count(), 10u); + BOOST_TEST_LT(x.bucket_count(), 1000u); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + } + + { + BOOST_TEST_GT(x.bucket_count(), 0u); + BOOST_TEST_LT(x.bucket_count(), 1000u); + + x.rehash(1000); + BOOST_TEST_GE(x.bucket_count(), 1000u); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + x.rehash(0); + BOOST_TEST_EQ(x.bucket_count(), 0u); + BOOST_TEST_EQ(test::detail::tracker.count_allocations, 0u); + } + } + + for (std::size_t i = 0; i < max_load_factors_len; ++i) { + typedef typename X::size_type size_type; + + X x; + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(test::detail::tracker.count_allocations, 0u); + + float const mlf = max_load_factors[i]; + x.max_load_factor(mlf); + + { + BOOST_TEST_EQ(x.bucket_count(), 0u); + + x.reserve(0); + BOOST_TEST_EQ(x.bucket_count(), 0u); + BOOST_TEST_EQ(test::detail::tracker.count_allocations, 0u); + } + + { + BOOST_TEST_EQ(x.bucket_count(), 0u); + + x.reserve(1000); + BOOST_TEST_GE( + x.bucket_count(), static_cast(std::ceil(1000 / mlf))); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + x.reserve(0); + BOOST_TEST_EQ(x.bucket_count(), 0u); + BOOST_TEST_EQ(test::detail::tracker.count_allocations, 0u); + } + + { + BOOST_TEST_EQ(x.bucket_count(), 0u); + + x.reserve(1000); + BOOST_TEST_GE( + x.bucket_count(), static_cast(std::ceil(1000 / mlf))); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + x.reserve(10); + BOOST_TEST_GE( + x.bucket_count(), static_cast(std::ceil(10 / mlf))); + BOOST_TEST_LT(x.bucket_count(), 1000u); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + } + + { + BOOST_TEST_GT(x.bucket_count(), 0u); + BOOST_TEST_LT(x.bucket_count(), 1000u); + + x.reserve(1000); + BOOST_TEST_GE( + x.bucket_count(), static_cast(std::ceil(1000 / mlf))); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + x.reserve(0); + BOOST_TEST_EQ(x.bucket_count(), 0u); + BOOST_TEST_EQ(test::detail::tracker.count_allocations, 0u); + } + } + } + + template + void rehash_nonempty_tracking(X*, test::random_generator generator) + { + test::random_values const v(1000, generator); + + typedef typename X::size_type size_type; + + float const max_load_factors[] = {0.5f, 1.0f, 1e2f}; + + size_type const max_load_factors_len = + sizeof(max_load_factors) / sizeof(*max_load_factors); + + for (size_type i = 0; i < max_load_factors_len; ++i) { + float const mlf = max_load_factors[i]; + + X x(v.begin(), v.end()); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + x.max_load_factor(mlf); + + size_type bucket_count = x.bucket_count(); + + { + BOOST_TEST_GT(x.bucket_count(), 0u); + + x.rehash(0); + BOOST_TEST_GE(x.bucket_count(), + static_cast( + std::floor(static_cast(x.size()) / x.max_load_factor()))); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + bucket_count = x.bucket_count(); + } + + { + BOOST_TEST_GT(bucket_count, 0u); + + x.rehash(2 * x.bucket_count()); + BOOST_TEST_GT(x.bucket_count(), bucket_count); + + bucket_count = x.bucket_count(); + } + + { + float const old_mlf = x.max_load_factor(); + + BOOST_TEST_GT(bucket_count, 0u); + + x.rehash(bucket_count / 4); + BOOST_TEST_LT(x.bucket_count(), bucket_count); + + x.max_load_factor(std::numeric_limits::infinity()); + x.rehash(0); + BOOST_TEST_GT(x.bucket_count(), 0u); + + x.max_load_factor(old_mlf); + } + + { + std::size_t const max_load = + static_cast(static_cast(x.max_load_factor()) * + static_cast(x.bucket_count())); + + while (x.size() < max_load) { + test::random_values const t(max_load, generator); + typename test::random_values::const_iterator pos = t.begin(); + typename test::random_values::const_iterator end = t.end(); + for (; pos != end; ++pos) { + x.insert(*pos); + if (x.size() == max_load) { + break; + } + } + } + + while (x.size() > max_load) { + x.erase(x.begin()); + } + + BOOST_TEST_EQ(x.size(), max_load); + + bucket_count = x.bucket_count(); + x.rehash(x.bucket_count()); + BOOST_TEST_EQ(x.bucket_count(), bucket_count); + } + } + + for (size_type i = 0; i < max_load_factors_len; ++i) { + X x(v.begin(), v.end()); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + float const mlf = max_load_factors[i]; + x.max_load_factor(mlf); + + size_type bucket_count = x.bucket_count(); + + { + BOOST_TEST_GT(x.bucket_count(), 0u); + + x.reserve(0); + BOOST_TEST_GE(x.bucket_count(), + static_cast( + std::floor(static_cast(x.size()) / x.max_load_factor()))); + BOOST_TEST_GT(test::detail::tracker.count_allocations, 0u); + + bucket_count = x.bucket_count(); + } + + { + BOOST_TEST_GT(x.bucket_count(), 0u); + + x.reserve( + 2 * (static_cast( + std::floor(static_cast(x.size()) / x.max_load_factor()) + + std::floor(static_cast(x.size()) * x.max_load_factor())))); + + BOOST_TEST_GT(x.bucket_count(), bucket_count); + + bucket_count = x.bucket_count(); + BOOST_TEST_GT(bucket_count, 1u); + } + + { + float const old_mlf = x.max_load_factor(); + + BOOST_TEST_GT(bucket_count, 4u); + + x.reserve(bucket_count / 4); + BOOST_TEST_LT(x.bucket_count(), bucket_count); + + x.max_load_factor(std::numeric_limits::infinity()); + x.reserve(0); + BOOST_TEST_GT(x.bucket_count(), 0u); + + x.max_load_factor(old_mlf); + } + + { + std::size_t const max_load = + static_cast(static_cast(x.max_load_factor()) * + static_cast(x.bucket_count())); + + while (x.size() < max_load) { + test::random_values const t(max_load, generator); + typename test::random_values::const_iterator pos = t.begin(); + typename test::random_values::const_iterator end = t.end(); + for (; pos != end; ++pos) { + x.insert(*pos); + if (x.size() == max_load) { + break; + } + } + } + + while (x.size() > max_load) { + x.erase(x.begin()); + } + + BOOST_TEST_EQ(x.size(), max_load); + + bucket_count = x.bucket_count(); + x.reserve(x.size()); + BOOST_TEST_EQ(x.bucket_count(), bucket_count); + } + } + } + template void rehash_test1(X*, test::random_generator generator) { test::random_values v(1000, generator); @@ -199,6 +493,18 @@ namespace rehash_tests { test::allocator2 >* test_map_ptr; boost::unordered_multimap* int_multimap_ptr; + boost::unordered_set >* test_set_tracking; + boost::unordered_multiset >* test_multiset_tracking; + boost::unordered_map > >* + test_map_tracking; + boost::unordered_multimap > >* + test_multimap_tracking; + using test::default_generator; using test::generate_collisions; using test::limited_range; @@ -224,6 +530,12 @@ namespace rehash_tests { UNORDERED_TEST(reserve_test2, ((int_set_ptr)(test_multiset_ptr)(test_map_ptr)(int_multimap_ptr))( (default_generator)(generate_collisions)(limited_range))) + UNORDERED_TEST(rehash_empty_tracking, + ((test_set_tracking)(test_multiset_tracking)(test_map_tracking)(test_multimap_tracking))( + (default_generator)(generate_collisions)(limited_range))) + UNORDERED_TEST(rehash_nonempty_tracking, + ((test_set_tracking)(test_multiset_tracking)(test_map_tracking)(test_multimap_tracking))( + (default_generator)(generate_collisions)(limited_range))) } RUN_TESTS()