Skip to content

Commit 00137b8

Browse files
committed
py/map: Change hash-table allocation policy to be less aggressive.
Small hash tables (eg those used in user class instances that only have a few members) now only use the minimum amount of memory necessary to hold the key/value pairs. This can reduce performance for instances that have many members (because then there are many reallocations/rehashings of the table), but helps to conserve memory. See issue adafruit#1760.
1 parent 5801967 commit 00137b8

1 file changed

Lines changed: 18 additions & 11 deletions

File tree

py/map.c

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,26 @@ const mp_map_t mp_const_empty_map = {
4545
.table = NULL,
4646
};
4747

48-
// approximatelly doubling primes; made with Mathematica command: Table[Prime[Floor[(1.7)^n]], {n, 3, 24}]
49-
// prefixed with zero for the empty case.
50-
STATIC uint32_t doubling_primes[] = {0, 7, 19, 43, 89, 179, 347, 647, 1229, 2297, 4243, 7829, 14347, 26017, 47149, 84947, 152443, 273253, 488399, 869927, 1547173, 2745121, 4861607};
51-
52-
STATIC mp_uint_t get_doubling_prime_greater_or_equal_to(mp_uint_t x) {
53-
for (size_t i = 0; i < MP_ARRAY_SIZE(doubling_primes); i++) {
54-
if (doubling_primes[i] >= x) {
55-
return doubling_primes[i];
48+
// This table of sizes is used to control the growth of hash tables.
49+
// The first set of sizes are chosen so the allocation fits exactly in a
50+
// 4-word GC block, and it's not so important for these small values to be
51+
// prime. The latter sizes are prime and increase at an increasing rate.
52+
STATIC uint16_t hash_allocation_sizes[] = {
53+
0, 2, 4, 6, 8, 10, 12, // +2
54+
17, 23, 29, 37, 47, 59, 73, // *1.25
55+
97, 127, 167, 223, 293, 389, 521, 691, 919, 1223, 1627, 2161, // *1.33
56+
3229, 4831, 7243, 10861, 16273, 24407, 36607, 54907, // *1.5
57+
};
58+
59+
STATIC mp_uint_t get_hash_alloc_greater_or_equal_to(mp_uint_t x) {
60+
for (size_t i = 0; i < MP_ARRAY_SIZE(hash_allocation_sizes); i++) {
61+
if (hash_allocation_sizes[i] >= x) {
62+
return hash_allocation_sizes[i];
5663
}
5764
}
5865
// ran out of primes in the table!
5966
// return something sensible, at least make it odd
60-
return x | 1;
67+
return (x + x / 2) | 1;
6168
}
6269

6370
/******************************************************************************/
@@ -118,7 +125,7 @@ void mp_map_clear(mp_map_t *map) {
118125

119126
STATIC void mp_map_rehash(mp_map_t *map) {
120127
mp_uint_t old_alloc = map->alloc;
121-
mp_uint_t new_alloc = get_doubling_prime_greater_or_equal_to(map->alloc + 1);
128+
mp_uint_t new_alloc = get_hash_alloc_greater_or_equal_to(map->alloc + 1);
122129
mp_map_elem_t *old_table = map->table;
123130
mp_map_elem_t *new_table = m_new0(mp_map_elem_t, new_alloc);
124131
// If we reach this point, table resizing succeeded, now we can edit the old map.
@@ -298,7 +305,7 @@ void mp_set_init(mp_set_t *set, mp_uint_t n) {
298305
STATIC void mp_set_rehash(mp_set_t *set) {
299306
mp_uint_t old_alloc = set->alloc;
300307
mp_obj_t *old_table = set->table;
301-
set->alloc = get_doubling_prime_greater_or_equal_to(set->alloc + 1);
308+
set->alloc = get_hash_alloc_greater_or_equal_to(set->alloc + 1);
302309
set->used = 0;
303310
set->table = m_new0(mp_obj_t, set->alloc);
304311
for (mp_uint_t i = 0; i < old_alloc; i++) {

0 commit comments

Comments
 (0)