Skip to content

Commit 8b5571f

Browse files
authored
Fix multi FlatMap scale and size (#2669)
1 parent 5f991fe commit 8b5571f

4 files changed

Lines changed: 158 additions & 58 deletions

File tree

src/butil/bit_array.h

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,22 @@
2828

2929
namespace butil {
3030

31+
#define BIT_ARRAY_LEN(nbit) (((nbit) + 63 ) / 64 * 8)
32+
3133
// Create an array with at least |nbit| bits. The array is not cleared.
32-
inline uint64_t* bit_array_malloc(size_t nbit)
33-
{
34+
inline uint64_t* bit_array_malloc(size_t nbit) {
3435
if (!nbit) {
3536
return NULL;
3637
}
37-
return (uint64_t*)malloc((nbit + 63 ) / 64 * 8/*different from /8*/);
38+
return (uint64_t*)malloc(BIT_ARRAY_LEN(nbit)/*different from /8*/);
39+
}
40+
41+
inline void bit_array_free(uint64_t* array) {
42+
free(array);
3843
}
3944

4045
// Set bit 0 ~ nbit-1 of |array| to be 0
41-
inline void bit_array_clear(uint64_t* array, size_t nbit)
42-
{
46+
inline void bit_array_clear(uint64_t* array, size_t nbit) {
4347
const size_t off = (nbit >> 6);
4448
memset(array, 0, off * 8);
4549
const size_t last = (off << 6);
@@ -49,31 +53,27 @@ inline void bit_array_clear(uint64_t* array, size_t nbit)
4953
}
5054

5155
// Set i-th bit (from left, counting from 0) of |array| to be 1
52-
inline void bit_array_set(uint64_t* array, size_t i)
53-
{
56+
inline void bit_array_set(uint64_t* array, size_t i) {
5457
const size_t off = (i >> 6);
5558
array[off] |= (((uint64_t)1) << (i - (off << 6)));
5659
}
5760

5861
// Set i-th bit (from left, counting from 0) of |array| to be 0
59-
inline void bit_array_unset(uint64_t* array, size_t i)
60-
{
62+
inline void bit_array_unset(uint64_t* array, size_t i) {
6163
const size_t off = (i >> 6);
6264
array[off] &= ~(((uint64_t)1) << (i - (off << 6)));
6365
}
6466

6567
// Get i-th bit (from left, counting from 0) of |array|
66-
inline uint64_t bit_array_get(const uint64_t* array, size_t i)
67-
{
68+
inline uint64_t bit_array_get(const uint64_t* array, size_t i) {
6869
const size_t off = (i >> 6);
6970
return (array[off] & (((uint64_t)1) << (i - (off << 6))));
7071
}
7172

7273
// Find index of first 1-bit from bit |begin| to |end| in |array|.
7374
// Returns |end| if all bits are 0.
7475
// This function is of O(nbit) complexity.
75-
inline size_t bit_array_first1(const uint64_t* array, size_t begin, size_t end)
76-
{
76+
inline size_t bit_array_first1(const uint64_t* array, size_t begin, size_t end) {
7777
size_t off1 = (begin >> 6);
7878
const size_t first = (off1 << 6);
7979
if (first != begin) {

src/butil/containers/flat_map.h

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
#include "butil/containers/hash_tables.h" // hash<>
105105
#include "butil/bit_array.h" // bit_array_*
106106
#include "butil/strings/string_piece.h" // StringPiece
107+
#include "butil/memory/scope_guard.h"
107108

108109
namespace butil {
109110

@@ -290,6 +291,18 @@ class FlatMap {
290291
template <typename _Map, typename _Element> friend class FlatMapIterator;
291292
template <typename _Map, typename _Element> friend class SparseFlatMapIterator;
292293

294+
struct NewBucketsInfo {
295+
NewBucketsInfo()
296+
: buckets(NULL), thumbnail(NULL), nbucket(0) {}
297+
NewBucketsInfo(Bucket* b, uint64_t* t, size_t n)
298+
: buckets(b), thumbnail(t), nbucket(n) {}
299+
Bucket* buckets;
300+
uint64_t* thumbnail;
301+
size_t nbucket;
302+
};
303+
304+
NewBucketsInfo new_buckets_and_thumbnail(size_t size, size_t new_nbucket);
305+
293306
// For `_Multi=true'.
294307
// Insert a new default-constructed associated with |key| always.
295308
// If size()*100/bucket_count() is more than load_factor,
@@ -299,8 +312,23 @@ template <typename _Map, typename _Element> friend class SparseFlatMapIterator;
299312
typename std::enable_if<Multi, mapped_type&>::type operator[](const key_type& key);
300313

301314
// True if buckets need to be resized before holding `size' elements.
302-
inline bool is_too_crowded(size_t size) const
303-
{ return size * 100 >= _nbucket * _load_factor; }
315+
bool is_too_crowded(size_t size) const {
316+
return is_too_crowded(size, _nbucket, _load_factor);
317+
}
318+
static bool is_too_crowded(size_t size, size_t nbucket, u_int load_factor) {
319+
return size * 100 >= nbucket * load_factor;
320+
}
321+
322+
static void init_buckets_and_thumbnail(
323+
Bucket* buckets, uint64_t* thumbnail, size_t nbucket) {
324+
for (size_t i = 0; i < nbucket; ++i) {
325+
buckets[i].set_invalid();
326+
}
327+
buckets[nbucket].next = NULL;
328+
if (_Sparse) {
329+
bit_array_clear(thumbnail, nbucket);
330+
}
331+
}
304332

305333
size_t _size;
306334
size_t _nbucket;

src/butil/containers/flat_map_inl.h

Lines changed: 88 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ FlatMap<_K, _T, _H, _E, _S, _A, _M>::~FlatMap() {
242242
clear();
243243
get_allocator().Free(_buckets);
244244
_buckets = NULL;
245-
free(_thumbnail);
245+
bit_array_free(_thumbnail);
246246
_thumbnail = NULL;
247247
_nbucket = 0;
248248
_load_factor = 0;
@@ -272,35 +272,32 @@ FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator=(const FlatMap<_K, _T, _H, _E, _S,
272272
if (!rhs.initialized()) {
273273
return *this;
274274
}
275-
bool need_copy = !rhs.empty();
276275
_load_factor = rhs._load_factor;
277276
if (_buckets == NULL || is_too_crowded(rhs._size)) {
278-
get_allocator().Free(_buckets);
279-
_nbucket = rhs._nbucket;
280-
// note: need an extra bucket to let iterator know where buckets end
281-
_buckets = (Bucket*)get_allocator().Alloc(sizeof(Bucket) * (_nbucket + 1/*note*/));
282-
if (NULL == _buckets) {
283-
LOG(ERROR) << "Fail to new _buckets";
277+
NewBucketsInfo info = new_buckets_and_thumbnail(_size, rhs._nbucket);
278+
if (0 == info.nbucket) {
279+
LOG(ERROR) << "Invalid nbucket=0";
284280
return *this;
285281
}
286-
// If no need to copy, set buckets invalid.
287-
if (!need_copy) {
288-
for (size_t i = 0; i < _nbucket; ++i) {
289-
_buckets[i].set_invalid();
290-
}
291-
_buckets[_nbucket].next = NULL;
282+
if (NULL == info.buckets) {
283+
LOG(ERROR) << "Fail to new buckets";
284+
return *this;
292285
}
286+
if (_S && NULL == info.thumbnail) {
287+
LOG(ERROR) << "Fail to new thumbnail";
288+
return *this;
289+
}
290+
291+
_nbucket = info.nbucket;
292+
get_allocator().Free(_buckets);
293+
_buckets = info.buckets;
293294
if (_S) {
294-
free(_thumbnail);
295-
_thumbnail = bit_array_malloc(_nbucket);
296-
if (NULL == _thumbnail) {
297-
LOG(ERROR) << "Fail to new _thumbnail";
298-
return *this;
299-
}
300-
bit_array_clear(_thumbnail, _nbucket);
295+
bit_array_free(_thumbnail);
296+
_thumbnail = info.thumbnail;
301297
}
302298
}
303-
if (!need_copy) {
299+
if (rhs.empty()) {
300+
// No need to copy, returns directly.
304301
return *this;
305302
}
306303
if (_nbucket == rhs._nbucket) {
@@ -327,7 +324,7 @@ FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator=(const FlatMap<_K, _T, _H, _E, _S,
327324
_size = rhs._size;
328325
} else {
329326
for (const_iterator it = rhs.begin(); it != rhs.end(); ++it) {
330-
operator[](Element::first_ref_from_value(*it)) =
327+
operator[](Element::first_ref_from_value(*it)) =
331328
Element::second_ref_from_value(*it);
332329
}
333330
}
@@ -341,33 +338,33 @@ int FlatMap<_K, _T, _H, _E, _S, _A, _M>::init(size_t nbucket, u_int load_factor)
341338
return -1;
342339
}
343340
if (nbucket == 0) {
344-
LOG(WARNING) << "Fail to init FlatMap, nbucket=" << nbucket;
341+
LOG(WARNING) << "Fail to init FlatMap, nbucket=" << nbucket;
345342
return -1;
346343
}
347344
if (load_factor < 10 || load_factor > 100) {
348345
LOG(ERROR) << "Invalid load_factor=" << load_factor;
349346
return -1;
350347
}
351348
_size = 0;
352-
_nbucket = flatmap_round(nbucket);
353349
_load_factor = load_factor;
354-
355-
_buckets = (Bucket*)get_allocator().Alloc(sizeof(Bucket) * (_nbucket + 1));
356-
if (NULL == _buckets) {
357-
LOG(ERROR) << "Fail to new _buckets";
350+
351+
NewBucketsInfo info = new_buckets_and_thumbnail(_size, nbucket);
352+
if (0 == info.nbucket) {
353+
LOG(ERROR) << "Invalid nbucket=0";
358354
return -1;
359355
}
360-
for (size_t i = 0; i < _nbucket; ++i) {
361-
_buckets[i].set_invalid();
356+
if (NULL == info.buckets) {
357+
LOG(ERROR) << "Fail to new buckets";
358+
return -1;
362359
}
363-
_buckets[_nbucket].next = NULL;
364-
360+
if (_S && NULL == info.thumbnail) {
361+
LOG(ERROR) << "Fail to new thumbnail";
362+
return -1;
363+
}
364+
_nbucket = info.nbucket;
365+
_buckets = info.buckets;
365366
if (_S) {
366-
_thumbnail = bit_array_malloc(_nbucket);
367-
if (NULL == _thumbnail) {
368-
LOG(ERROR) << "Fail to new _thumbnail";
369-
return -1;
370-
}
367+
_thumbnail = info.thumbnail;
371368
bit_array_clear(_thumbnail, _nbucket);
372369
}
373370
return 0;
@@ -625,7 +622,7 @@ FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator[](const key_type& key) {
625622
return first_node.element().second_ref();
626623
}
627624
Bucket *p = &first_node;
628-
while (1) {
625+
while (true) {
629626
if (_eql(p->element().first_ref(), key)) {
630627
return p->element().second_ref();
631628
}
@@ -659,6 +656,24 @@ FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator[](const key_type& key) {
659656
new (&first_node) Bucket(key);
660657
return first_node.element().second_ref();
661658
}
659+
if (is_too_crowded(_size)) {
660+
Bucket *p = &first_node;
661+
bool need_scale = false;
662+
while (NULL != p) {
663+
// Increase the capacity of bucket when
664+
// hash collision occur and map is crowded.
665+
if (!_eql(p->element().first_ref(), key)) {
666+
need_scale = true;
667+
break;
668+
}
669+
p = p->next;
670+
}
671+
if (need_scale && resize(_nbucket + 1)) {
672+
return operator[](key);
673+
}
674+
// fail to resize is OK
675+
}
676+
++_size;
662677
Bucket* newp = new (_pool.get()) Bucket(key);
663678
newp->next = first_node.next;
664679
first_node.next = newp;
@@ -720,15 +735,15 @@ bool FlatMap<_K, _T, _H, _E, _S, _A, _M>::resize(size_t nbucket2) {
720735
return false;
721736
}
722737

723-
// NOTE: following functors must be kept after resizing otherwise the
738+
// NOTE: following functors must be kept after resizing otherwise the
724739
// internal state is lost.
725740
FlatMap new_map(_hashfn, _eql, get_allocator());
726741
if (new_map.init(nbucket2, _load_factor) != 0) {
727742
LOG(ERROR) << "Fail to init new_map, nbucket=" << nbucket2;
728743
return false;
729744
}
730745
for (iterator it = begin(); it != end(); ++it) {
731-
new_map[Element::first_ref_from_value(*it)] =
746+
new_map[Element::first_ref_from_value(*it)] =
732747
Element::second_movable_ref_from_value(*it);
733748
}
734749
new_map.swap(*this);
@@ -751,6 +766,38 @@ BucketInfo FlatMap<_K, _T, _H, _E, _S, _A, _M>::bucket_info() const {
751766
return info;
752767
}
753768

769+
template <typename _K, typename _T, typename _H, typename _E, bool _S, typename _A, bool _M>
770+
typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::NewBucketsInfo
771+
FlatMap<_K, _T, _H, _E, _S, _A, _M>::new_buckets_and_thumbnail(size_t size,
772+
size_t new_nbucket) {
773+
do {
774+
new_nbucket = flatmap_round(new_nbucket);
775+
} while (is_too_crowded(size, new_nbucket, _load_factor));
776+
// Note: need an extra bucket to let iterator know where buckets end.
777+
auto buckets = (Bucket*)get_allocator().Alloc(sizeof(Bucket) * (
778+
new_nbucket + 1/*note*/));
779+
auto guard = MakeScopeGuard([buckets, this]() {
780+
get_allocator().Free(buckets);
781+
});
782+
if (NULL == buckets) {
783+
LOG(FATAL) << "Fail to new Buckets";
784+
return {};
785+
}
786+
787+
uint64_t* thumbnail = NULL;
788+
if (_S) {
789+
thumbnail = bit_array_malloc(new_nbucket);
790+
if (NULL == thumbnail) {
791+
LOG(FATAL) << "Fail to new thumbnail";
792+
return {};
793+
}
794+
}
795+
796+
guard.dismiss();
797+
init_buckets_and_thumbnail(buckets, thumbnail, new_nbucket);
798+
return { buckets, thumbnail, new_nbucket };
799+
}
800+
754801
inline std::ostream& operator<<(std::ostream& os, const BucketInfo& info) {
755802
return os << "{maxb=" << info.longest_length
756803
<< " avgb=" << info.average_length << '}';

0 commit comments

Comments
 (0)