Skip to content

Commit 5b85ee3

Browse files
committed
py/map,qstr.c: Split into map_opt,qstr_opt.c for CSUPEROPT.
This allows a perf-sensitive subset of these files to have CSUPEROPT enabled, giving a ~3% performance improvement for a cost of 536 bytes. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent d8c0f99 commit 5b85ee3

File tree

8 files changed

+558
-476
lines changed

8 files changed

+558
-476
lines changed

py/map.c

Lines changed: 1 addition & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,6 @@
4040
#define DEBUG_printf(...) (void)0
4141
#endif
4242

43-
#if MICROPY_OPT_MAP_LOOKUP_CACHE
44-
// MP_STATE_VM(map_lookup_cache) provides a cache of index to the last known
45-
// position of that index in any map. On a cache hit, this allows
46-
// short-circuiting the full linear search in the case of an ordered map
47-
// (i.e. all builtin modules and objects' locals dicts), and computation of
48-
// the hash (and potentially some linear probing) in the case of a regular
49-
// map. Note the same cache is shared across all maps.
50-
51-
// Gets the index into the cache for this index. Shift down by two to remove
52-
// mp_obj_t tag bits.
53-
#define MAP_CACHE_OFFSET(index) ((((uintptr_t)(index)) >> 2) % MICROPY_OPT_MAP_LOOKUP_CACHE_SIZE)
54-
// Gets the map cache entry for the corresponding index.
55-
#define MAP_CACHE_ENTRY(index) (MP_STATE_VM(map_lookup_cache)[MAP_CACHE_OFFSET(index)])
56-
// Retrieve the mp_obj_t at the location suggested by the cache.
57-
#define MAP_CACHE_GET(map, index) (&(map)->table[MAP_CACHE_ENTRY(index) % (map)->alloc])
58-
// Update the cache for this index.
59-
#define MAP_CACHE_SET(index, pos) MAP_CACHE_ENTRY(index) = (pos) & 0xff;
60-
#else
61-
#define MAP_CACHE_SET(index, pos)
62-
#endif
63-
6443
// This table of sizes is used to control the growth of hash tables.
6544
// The first set of sizes are chosen so the allocation fits exactly in a
6645
// 4-word GC block, and it's not so important for these small values to be
@@ -128,7 +107,7 @@ void mp_map_clear(mp_map_t *map) {
128107
map->table = NULL;
129108
}
130109

131-
STATIC void mp_map_rehash(mp_map_t *map) {
110+
void mp_map_rehash(mp_map_t *map) {
132111
size_t old_alloc = map->alloc;
133112
size_t new_alloc = get_hash_alloc_greater_or_equal_to(map->alloc + 1);
134113
DEBUG_printf("mp_map_rehash(%p): " UINT_FMT " -> " UINT_FMT "\n", map, old_alloc, new_alloc);
@@ -147,209 +126,6 @@ STATIC void mp_map_rehash(mp_map_t *map) {
147126
m_del(mp_map_elem_t, old_table, old_alloc);
148127
}
149128

150-
// MP_MAP_LOOKUP behaviour:
151-
// - returns NULL if not found, else the slot it was found in with key,value non-null
152-
// MP_MAP_LOOKUP_ADD_IF_NOT_FOUND behaviour:
153-
// - returns slot, with key non-null and value=MP_OBJ_NULL if it was added
154-
// MP_MAP_LOOKUP_REMOVE_IF_FOUND behaviour:
155-
// - returns NULL if not found, else the slot if was found in with key null and value non-null
156-
mp_map_elem_t *MICROPY_WRAP_MP_MAP_LOOKUP(mp_map_lookup)(mp_map_t * map, mp_obj_t index, mp_map_lookup_kind_t lookup_kind) {
157-
// If the map is a fixed array then we must only be called for a lookup
158-
assert(!map->is_fixed || lookup_kind == MP_MAP_LOOKUP);
159-
160-
#if MICROPY_OPT_MAP_LOOKUP_CACHE
161-
// Try the cache for lookup or add-if-not-found.
162-
if (lookup_kind != MP_MAP_LOOKUP_REMOVE_IF_FOUND && map->alloc) {
163-
mp_map_elem_t *slot = MAP_CACHE_GET(map, index);
164-
// Note: Just comparing key for value equality will have false negatives, but
165-
// these will be handled by the regular path below.
166-
if (slot->key == index) {
167-
return slot;
168-
}
169-
}
170-
#endif
171-
172-
// Work out if we can compare just pointers
173-
bool compare_only_ptrs = map->all_keys_are_qstrs;
174-
if (compare_only_ptrs) {
175-
if (mp_obj_is_qstr(index)) {
176-
// Index is a qstr, so can just do ptr comparison.
177-
} else if (mp_obj_is_exact_type(index, &mp_type_str)) {
178-
// Index is a non-interned string.
179-
// We can either intern the string, or force a full equality comparison.
180-
// We chose the latter, since interning costs time and potentially RAM,
181-
// and it won't necessarily benefit subsequent calls because these calls
182-
// most likely won't pass the newly-interned string.
183-
compare_only_ptrs = false;
184-
} else if (lookup_kind != MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) {
185-
// If we are not adding, then we can return straight away a failed
186-
// lookup because we know that the index will never be found.
187-
return NULL;
188-
}
189-
}
190-
191-
// if the map is an ordered array then we must do a brute force linear search
192-
if (map->is_ordered) {
193-
for (mp_map_elem_t *elem = &map->table[0], *top = &map->table[map->used]; elem < top; elem++) {
194-
if (elem->key == index || (!compare_only_ptrs && mp_obj_equal(elem->key, index))) {
195-
#if MICROPY_PY_COLLECTIONS_ORDEREDDICT
196-
if (MP_UNLIKELY(lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND)) {
197-
// remove the found element by moving the rest of the array down
198-
mp_obj_t value = elem->value;
199-
--map->used;
200-
memmove(elem, elem + 1, (top - elem - 1) * sizeof(*elem));
201-
// put the found element after the end so the caller can access it if needed
202-
// note: caller must NULL the value so the GC can clean up (e.g. see dict_get_helper).
203-
elem = &map->table[map->used];
204-
elem->key = MP_OBJ_NULL;
205-
elem->value = value;
206-
}
207-
#endif
208-
MAP_CACHE_SET(index, elem - map->table);
209-
return elem;
210-
}
211-
}
212-
#if MICROPY_PY_COLLECTIONS_ORDEREDDICT
213-
if (MP_LIKELY(lookup_kind != MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)) {
214-
return NULL;
215-
}
216-
if (map->used == map->alloc) {
217-
// TODO: Alloc policy
218-
map->alloc += 4;
219-
map->table = m_renew(mp_map_elem_t, map->table, map->used, map->alloc);
220-
mp_seq_clear(map->table, map->used, map->alloc, sizeof(*map->table));
221-
}
222-
mp_map_elem_t *elem = map->table + map->used++;
223-
elem->key = index;
224-
elem->value = MP_OBJ_NULL;
225-
if (!mp_obj_is_qstr(index)) {
226-
map->all_keys_are_qstrs = 0;
227-
}
228-
return elem;
229-
#else
230-
return NULL;
231-
#endif
232-
}
233-
234-
// map is a hash table (not an ordered array), so do a hash lookup
235-
236-
if (map->alloc == 0) {
237-
if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) {
238-
mp_map_rehash(map);
239-
} else {
240-
return NULL;
241-
}
242-
}
243-
244-
// When MICROPY_QSTR_BYTES_IN_HASH is zero, for qstr keys we use the qstr
245-
// value (i.e. the index in the qstr pool) as the hash value as it is
246-
// free to compute and also a very effective hash as it is unique across
247-
// all qstrs.
248-
//
249-
// However, non-qstr strings (i.e. str) still use their "true" hash
250-
// (i.e. computed using qstr_hash) which will be different. So this means
251-
// that identical strings will hash differently depending on whether they
252-
// are qstr or str. It should be rare for a program to be working with
253-
// both but it is possible.
254-
//
255-
// So this means that in certain situations (see below), we cannot rely on
256-
// the linear probing finding an unused slot as the signal that the key
257-
// does not exist, as it's possible the key was inserted using
258-
// its "other" hash, and must fall back to searching the entire table. We
259-
// can still use the hash as a good hint for the starting location
260-
// though. This flag must be set to false in these situations.
261-
bool stop_at_empty = true;
262-
263-
// get hash of index, with fast path for common case of qstr
264-
mp_uint_t hash;
265-
if (mp_obj_is_qstr(index)) {
266-
#if MICROPY_QSTR_BYTES_IN_HASH
267-
hash = qstr_hash(MP_OBJ_QSTR_VALUE(index));
268-
#else
269-
// Optimisation -- use the qstr index directly as the hash.
270-
hash = MP_OBJ_QSTR_VALUE(index);
271-
// If there are non-qstr keys in this map, we must assume that there
272-
// may possibly be str keys.
273-
stop_at_empty = map->all_keys_are_qstrs;
274-
#endif
275-
} else {
276-
hash = MP_OBJ_SMALL_INT_VALUE(mp_unary_op(MP_UNARY_OP_HASH, index));
277-
278-
#if MICROPY_QSTR_BYTES_IN_HASH == 0
279-
if (mp_obj_is_exact_type(index, &mp_type_str)) {
280-
// We must assume there might be qstr keys.
281-
stop_at_empty = false;
282-
}
283-
#endif
284-
}
285-
286-
size_t pos = hash % map->alloc;
287-
size_t start_pos = pos;
288-
mp_map_elem_t *avail_slot = NULL;
289-
for (;;) {
290-
mp_map_elem_t *slot = &map->table[pos];
291-
if (slot->key == MP_OBJ_NULL) {
292-
// found an empty slot, remember for later
293-
if (avail_slot == NULL) {
294-
avail_slot = slot;
295-
}
296-
if (stop_at_empty) {
297-
// safe to assume that the key doesn't exist, so pretend like
298-
// we searched the entire table
299-
goto search_over;
300-
}
301-
} else if (slot->key == MP_OBJ_SENTINEL) {
302-
// found deleted slot, remember for later
303-
if (avail_slot == NULL) {
304-
avail_slot = slot;
305-
}
306-
} else if (slot->key == index || (!compare_only_ptrs && mp_obj_equal(slot->key, index))) {
307-
// found index
308-
// Note: CPython does not replace the index; try x={True:'true'};x[1]='one';x
309-
if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) {
310-
// delete element in this slot
311-
map->used--;
312-
if (map->table[(pos + 1) % map->alloc].key == MP_OBJ_NULL) {
313-
// optimisation if next slot is empty
314-
slot->key = MP_OBJ_NULL;
315-
} else {
316-
slot->key = MP_OBJ_SENTINEL;
317-
}
318-
// keep slot->value so that caller can access it if needed
319-
}
320-
MAP_CACHE_SET(index, pos);
321-
return slot;
322-
}
323-
324-
// not yet found, keep searching in this table
325-
pos = (pos + 1) % map->alloc;
326-
327-
if (pos == start_pos) {
328-
search_over:
329-
// search got back to starting position (or found empty slot), so index is not in table
330-
if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) {
331-
if (avail_slot != NULL) {
332-
// there was an available slot, so use that
333-
map->used++;
334-
avail_slot->key = index;
335-
avail_slot->value = MP_OBJ_NULL;
336-
if (!mp_obj_is_qstr(index)) {
337-
map->all_keys_are_qstrs = 0;
338-
}
339-
return avail_slot;
340-
} else {
341-
// not enough room in table, rehash it
342-
mp_map_rehash(map);
343-
// restart the search for the new element
344-
start_pos = pos = hash % map->alloc;
345-
}
346-
} else {
347-
return NULL;
348-
}
349-
}
350-
}
351-
}
352-
353129
/******************************************************************************/
354130
/* set */
355131

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy