Skip to content
7 changes: 7 additions & 0 deletions CHANGES/1200.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Stopped reallocating memory for the internal ``htkeys_t`` structure when inserting new items if the
multidict has deleted items and it could be collapsed in-place. Removal of
``malloc()``/``free()`` improves the performance slightly.

The change affects C implementation only, pure Python code is not changed.

Patch by :user:`asvetlov`.
43 changes: 40 additions & 3 deletions multidict/_multilib/hashtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ _md_resize(MultiDictObject *md, uint8_t log2_newsize, bool update)
} else {
entry_t *new_ep = newentries;
entry_t *old_ep = oldentries;
for (Py_ssize_t i = 0; i < oldkeys->nentries; ++i, ++old_ep) {
Py_ssize_t oldnumentries = oldkeys->nentries;
for (Py_ssize_t i = 0; i < oldnumentries; ++i, ++old_ep) {
if (old_ep->identity != NULL) {
*new_ep++ = *old_ep;
}
Expand All @@ -232,16 +233,52 @@ _md_resize(MultiDictObject *md, uint8_t log2_newsize, bool update)
return 0;
}

static inline int
_md_shrink(MultiDictObject *md, bool update)
{
htkeys_t *keys = md->keys;
Py_ssize_t nentries = keys->nentries;
entry_t *entries = htkeys_entries(keys);
entry_t *new_ep = entries;
entry_t *old_ep = entries;
Py_ssize_t newnentries = nentries;
for (Py_ssize_t i = 0; i < nentries; ++i, ++old_ep) {
if (old_ep->identity != NULL) {
if (new_ep != old_ep) {
*new_ep++ = *old_ep;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it might be the cause of the segfault

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does new_ep always need to be incremented here even if != ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe new_ep == old_ep, first entry to be overwritten by later entries, new_ep now at wrong position, anything after writes to the wrong memory address...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no.
The loop iterates over all elements and overwrites empty ones with next items.
It basically deletes entries with identity == NULL and collapses unused memory holes

}
} else {
newnentries -= 1;
}
}
keys->nentries = newnentries;
keys->usable += nentries - newnentries;
memset(&keys->indices[0], 0xff, ((size_t)1 << keys->log2_index_bytes));
if (htkeys_build_indices(keys, entries, newnentries, update) < 0) {
return -1;
}
ASSERT_CONSISTENT(md, update);
return 0;
}

static inline int
_md_resize_for_insert(MultiDictObject *md)
{
return _md_resize(md, calculate_log2_keysize(GROWTH_RATE(md)), false);
if (md->used < md->keys->nentries) {
return _md_shrink(md, false);
} else {
return _md_resize(md, calculate_log2_keysize(GROWTH_RATE(md)), false);
}
}

static inline int
_md_resize_for_update(MultiDictObject *md)
{
return _md_resize(md, calculate_log2_keysize(GROWTH_RATE(md)), true);
if (md->used < md->keys->nentries) {
return _md_shrink(md, true);
} else {
return _md_resize(md, calculate_log2_keysize(GROWTH_RATE(md)), true);
}
Comment thread
asvetlov marked this conversation as resolved.
}

static inline int
Expand Down
Loading