py/qstr: Add support for MICROPY_QSTR_BYTES_IN_HASH=0.

This disables using qstr hashes altogether, which saves RAM and flash
(two bytes per interned string on a typical build) as well as code size.
On PYBV11 this is worth over 3k flash.

qstr comparison will now be done just by length then data. This affects
qstr_find_strn although this has a negligible performance impact as, for a
given comparison, the length and first character will ~usually be
different anyway.

String hashing (e.g. builtin `hash()` and map.c) now need to compute the
hash dynamically, and for the map case this does come at a performance
cost.

This work was funded through GitHub Sponsors.

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
pull/12835/head
Jim Mussared 2023-02-03 16:42:03 +11:00
rodzic 307ecc5707
commit 7ea503929a
4 zmienionych plików z 66 dodań i 23 usunięć

Wyświetl plik

@ -295,7 +295,8 @@ def compute_hash(qstr, bytes_hash):
for b in qstr:
hash = (hash * 33) ^ b
# Make sure that valid hash is never zero, zero means "hash not computed"
return (hash & ((1 << (8 * bytes_hash)) - 1)) or 1
# if bytes_hash is zero, assume a 16-bit mask (to match qstr.c)
return (hash & ((1 << (8 * (bytes_hash or 2))) - 1)) or 1
def qstr_escape(qst):

Wyświetl plik

@ -42,7 +42,11 @@
// A qstr is an index into the qstr pool.
// The data for a qstr is \0 terminated (so they can be printed using printf)
#if MICROPY_QSTR_BYTES_IN_HASH
#define Q_HASH_MASK ((1 << (8 * MICROPY_QSTR_BYTES_IN_HASH)) - 1)
#else
#define Q_HASH_MASK (0xffff)
#endif
#if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL
#define QSTR_ENTER() mp_thread_mutex_lock(&MP_STATE_VM(qstr_mutex), 1)
@ -77,6 +81,7 @@ size_t qstr_compute_hash(const byte *data, size_t len) {
// future .mpy version we could re-order them and make it sorted). It also
// contains additional qstrs that must have IDs <256, see operator_qstr_list
// in makeqstrdata.py.
#if MICROPY_QSTR_BYTES_IN_HASH
const qstr_hash_t mp_qstr_const_hashes_static[] = {
#ifndef NO_QSTR
#define QDEF0(id, hash, len, str) hash,
@ -86,6 +91,7 @@ const qstr_hash_t mp_qstr_const_hashes_static[] = {
#undef QDEF1
#endif
};
#endif
const qstr_len_t mp_qstr_const_lengths_static[] = {
#ifndef NO_QSTR
@ -103,7 +109,9 @@ const qstr_pool_t mp_qstr_const_pool_static = {
false, // is_sorted
MICROPY_ALLOC_QSTR_ENTRIES_INIT,
MP_QSTRnumber_of_static, // corresponds to number of strings in array just below
#if MICROPY_QSTR_BYTES_IN_HASH
(qstr_hash_t *)mp_qstr_const_hashes_static,
#endif
(qstr_len_t *)mp_qstr_const_lengths_static,
{
#ifndef NO_QSTR
@ -118,6 +126,7 @@ const qstr_pool_t mp_qstr_const_pool_static = {
// The next pool is the remainder of the qstrs defined in the firmware. This
// is sorted.
#if MICROPY_QSTR_BYTES_IN_HASH
const qstr_hash_t mp_qstr_const_hashes[] = {
#ifndef NO_QSTR
#define QDEF0(id, hash, len, str)
@ -127,6 +136,7 @@ const qstr_hash_t mp_qstr_const_hashes[] = {
#undef QDEF1
#endif
};
#endif
const qstr_len_t mp_qstr_const_lengths[] = {
#ifndef NO_QSTR
@ -144,7 +154,9 @@ const qstr_pool_t mp_qstr_const_pool = {
true, // is_sorted
MICROPY_ALLOC_QSTR_ENTRIES_INIT,
MP_QSTRnumber_of - MP_QSTRnumber_of_static, // corresponds to number of strings in array just below
#if MICROPY_QSTR_BYTES_IN_HASH
(qstr_hash_t *)mp_qstr_const_hashes,
#endif
(qstr_len_t *)mp_qstr_const_lengths,
{
#ifndef NO_QSTR
@ -188,8 +200,13 @@ STATIC const qstr_pool_t *find_qstr(qstr *q) {
}
// qstr_mutex must be taken while in this function
STATIC qstr qstr_add(mp_uint_t hash, mp_uint_t len, const char *q_ptr) {
STATIC qstr qstr_add(mp_uint_t len, const char *q_ptr) {
#if MICROPY_QSTR_BYTES_IN_HASH
mp_uint_t hash = qstr_compute_hash((const byte *)q_ptr, len);
DEBUG_printf("QSTR: add hash=%d len=%d data=%.*s\n", hash, len, len, q_ptr);
#else
DEBUG_printf("QSTR: add len=%d data=%.*s\n", len, len, q_ptr);
#endif
// make sure we have room in the pool for a new qstr
if (MP_STATE_VM(last_pool)->len >= MP_STATE_VM(last_pool)->alloc) {
@ -199,7 +216,11 @@ STATIC qstr qstr_add(mp_uint_t hash, mp_uint_t len, const char *q_ptr) {
new_alloc = MAX(MICROPY_ALLOC_QSTR_ENTRIES_INIT, new_alloc);
#endif
mp_uint_t pool_size = sizeof(qstr_pool_t)
+ (sizeof(const char *) + sizeof(qstr_hash_t) + sizeof(qstr_len_t)) * new_alloc;
+ (sizeof(const char *)
#if MICROPY_QSTR_BYTES_IN_HASH
+ sizeof(qstr_hash_t)
#endif
+ sizeof(qstr_len_t)) * new_alloc;
qstr_pool_t *pool = (qstr_pool_t *)m_malloc_maybe(pool_size);
if (pool == NULL) {
// Keep qstr_last_chunk consistent with qstr_pool_t: qstr_last_chunk is not scanned
@ -211,8 +232,12 @@ STATIC qstr qstr_add(mp_uint_t hash, mp_uint_t len, const char *q_ptr) {
QSTR_EXIT();
m_malloc_fail(new_alloc);
}
#if MICROPY_QSTR_BYTES_IN_HASH
pool->hashes = (qstr_hash_t *)(pool->qstrs + new_alloc);
pool->lengths = (qstr_len_t *)(pool->hashes + new_alloc);
#else
pool->lengths = (qstr_len_t *)(pool->qstrs + new_alloc);
#endif
pool->prev = MP_STATE_VM(last_pool);
pool->total_prev_len = MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len;
pool->alloc = new_alloc;
@ -223,7 +248,9 @@ STATIC qstr qstr_add(mp_uint_t hash, mp_uint_t len, const char *q_ptr) {
// add the new qstr
mp_uint_t at = MP_STATE_VM(last_pool)->len;
#if MICROPY_QSTR_BYTES_IN_HASH
MP_STATE_VM(last_pool)->hashes[at] = hash;
#endif
MP_STATE_VM(last_pool)->lengths[at] = len;
MP_STATE_VM(last_pool)->qstrs[at] = q_ptr;
MP_STATE_VM(last_pool)->len++;
@ -238,8 +265,10 @@ qstr qstr_find_strn(const char *str, size_t str_len) {
return MP_QSTR_;
}
#if MICROPY_QSTR_BYTES_IN_HASH
// work out hash of str
size_t str_hash = qstr_compute_hash((const byte *)str, str_len);
#endif
// search pools for the data
for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL; pool = pool->prev) {
@ -261,7 +290,11 @@ qstr qstr_find_strn(const char *str, size_t str_len) {
// sequential search for the remaining strings
for (mp_uint_t at = low; at < high + 1; at++) {
if (pool->hashes[at] == str_hash && pool->lengths[at] == str_len
if (
#if MICROPY_QSTR_BYTES_IN_HASH
pool->hashes[at] == str_hash &&
#endif
pool->lengths[at] == str_len
&& memcmp(pool->qstrs[at], str, str_len) == 0) {
return pool->total_prev_len + at;
}
@ -329,10 +362,9 @@ qstr qstr_from_strn(const char *str, size_t len) {
MP_STATE_VM(qstr_last_used) += n_bytes;
// store the interned strings' data
size_t hash = qstr_compute_hash((const byte *)str, len);
memcpy(q_ptr, str, len);
q_ptr[len] = '\0';
q = qstr_add(hash, len, q_ptr);
q = qstr_add(len, q_ptr);
}
QSTR_EXIT();
return q;
@ -340,7 +372,11 @@ qstr qstr_from_strn(const char *str, size_t len) {
mp_uint_t qstr_hash(qstr q) {
const qstr_pool_t *pool = find_qstr(&q);
#if MICROPY_QSTR_BYTES_IN_HASH
return pool->hashes[q];
#else
return qstr_compute_hash((byte *)pool->qstrs[q], pool->lengths[q]);
#endif
}
size_t qstr_len(qstr q) {
@ -375,7 +411,11 @@ void qstr_pool_info(size_t *n_pool, size_t *n_qstr, size_t *n_str_data_bytes, si
*n_total_bytes += gc_nbytes(pool); // this counts actual bytes used in heap
#else
*n_total_bytes += sizeof(qstr_pool_t)
+ (sizeof(const char *) + sizeof(qstr_hash_t) + sizeof(qstr_len_t)) * pool->alloc;
+ (sizeof(const char *)
#if MICROPY_QSTR_BYTES_IN_HASH
+ sizeof(qstr_hash_t)
#endif
+ sizeof(qstr_len_t)) * pool->alloc;
#endif
}
*n_total_bytes += *n_str_data_bytes;

Wyświetl plik

@ -60,7 +60,9 @@ enum {
typedef size_t qstr;
typedef uint16_t qstr_short_t;
#if MICROPY_QSTR_BYTES_IN_HASH == 1
#if MICROPY_QSTR_BYTES_IN_HASH == 0
// No qstr_hash_t type needed.
#elif MICROPY_QSTR_BYTES_IN_HASH == 1
typedef uint8_t qstr_hash_t;
#elif MICROPY_QSTR_BYTES_IN_HASH == 2
typedef uint16_t qstr_hash_t;
@ -82,7 +84,9 @@ typedef struct _qstr_pool_t {
size_t is_sorted : 1;
size_t alloc;
size_t len;
#if MICROPY_QSTR_BYTES_IN_HASH
qstr_hash_t *hashes;
#endif
qstr_len_t *lengths;
const char *qstrs[];
} qstr_pool_t;
@ -92,6 +96,7 @@ typedef struct _qstr_pool_t {
void qstr_init(void);
size_t qstr_compute_hash(const byte *data, size_t len);
qstr qstr_find_strn(const char *str, size_t str_len); // returns MP_QSTRnull if not found
qstr qstr_from_str(const char *str);

Wyświetl plik

@ -1473,21 +1473,20 @@ def freeze_mpy(firmware_qstr_idents, compiled_modules):
raw_code_count = 0
raw_code_content = 0
print()
print("const qstr_hash_t mp_qstr_frozen_const_hashes[] = {")
qstr_size = {"metadata": 0, "data": 0}
for _, _, _, qbytes in new:
qhash = qstrutil.compute_hash(qbytes, config.MICROPY_QSTR_BYTES_IN_HASH)
print(" %d," % qhash)
print("};")
if config.MICROPY_QSTR_BYTES_IN_HASH:
print()
print("const qstr_hash_t mp_qstr_frozen_const_hashes[] = {")
for _, _, _, qbytes in new:
qhash = qstrutil.compute_hash(qbytes, config.MICROPY_QSTR_BYTES_IN_HASH)
print(" %d," % qhash)
qstr_content += config.MICROPY_QSTR_BYTES_IN_HASH
print("};")
print()
print("const qstr_len_t mp_qstr_frozen_const_lengths[] = {")
for _, _, _, qbytes in new:
print(" %d," % len(qbytes))
qstr_size["metadata"] += (
config.MICROPY_QSTR_BYTES_IN_LEN + config.MICROPY_QSTR_BYTES_IN_HASH
)
qstr_size["data"] += len(qbytes)
qstr_content += config.MICROPY_QSTR_BYTES_IN_LEN
qstr_content += len(qbytes) + 1 # include NUL
print("};")
print()
print("extern const qstr_pool_t mp_qstr_const_pool;")
@ -1497,14 +1496,12 @@ def freeze_mpy(firmware_qstr_idents, compiled_modules):
print(" true, // is_sorted")
print(" %u, // allocated entries" % qstr_pool_alloc)
print(" %u, // used entries" % len(new))
print(" (qstr_hash_t *)mp_qstr_frozen_const_hashes,")
if config.MICROPY_QSTR_BYTES_IN_HASH:
print(" (qstr_hash_t *)mp_qstr_frozen_const_hashes,")
print(" (qstr_len_t *)mp_qstr_frozen_const_lengths,")
print(" {")
for _, _, qstr, qbytes in new:
print(' "%s",' % qstrutil.escape_bytes(qstr, qbytes))
qstr_content += (
config.MICROPY_QSTR_BYTES_IN_LEN + config.MICROPY_QSTR_BYTES_IN_HASH + len(qbytes) + 1
)
print(" },")
print("};")