summaryrefslogtreecommitdiffstats
path: root/Objects
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2016-10-29 07:50:00 (GMT)
committerSerhiy Storchaka <storchaka@gmail.com>2016-10-29 07:50:00 (GMT)
commite26e20db95978a9401084f1dd7de75159ac98e25 (patch)
treec5cde8f761620a97fe47d0483b68cd81e7a9b210 /Objects
parent49d02d1659b4b642757aff5aab8dc3fa668a8bd2 (diff)
downloadcpython-e26e20db95978a9401084f1dd7de75159ac98e25.zip
cpython-e26e20db95978a9401084f1dd7de75159ac98e25.tar.gz
cpython-e26e20db95978a9401084f1dd7de75159ac98e25.tar.bz2
Issue #28199: Microoptimized dict resizing. Based on patch by Naoki Inada.
Diffstat (limited to 'Objects')
-rw-r--r--Objects/dictobject.c123
1 files changed, 63 insertions, 60 deletions
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 9bf4eee..cb67dda 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -1198,41 +1198,21 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
}
/*
-Internal routine used by dictresize() to insert an item which is
-known to be absent from the dict. This routine also assumes that
-the dict contains no deleted entries. Besides the performance benefit,
-using insertdict() in dictresize() is dangerous (SF bug #1456209).
-Note that no refcounts are changed by this routine; if needed, the caller
-is responsible for incref'ing `key` and `value`.
-Neither mp->ma_used nor k->dk_usable are modified by this routine; the caller
-must set them correctly
+Internal routine used by dictresize() to buid a hashtable of entries.
*/
static void
-insertdict_clean(PyDictObject *mp, PyObject *key, Py_hash_t hash,
- PyObject *value)
+build_indices(PyDictKeysObject *keys, PyDictKeyEntry *ep, Py_ssize_t n)
{
- size_t i;
- PyDictKeysObject *k = mp->ma_keys;
- size_t mask = (size_t)DK_SIZE(k)-1;
- PyDictKeyEntry *ep0 = DK_ENTRIES(mp->ma_keys);
- PyDictKeyEntry *ep;
-
- assert(k->dk_lookup != NULL);
- assert(value != NULL);
- assert(key != NULL);
- assert(PyUnicode_CheckExact(key) || k->dk_lookup == lookdict);
- i = hash & mask;
- for (size_t perturb = hash; dk_get_index(k, i) != DKIX_EMPTY;) {
- perturb >>= PERTURB_SHIFT;
- i = mask & ((i << 2) + i + perturb + 1);
+ size_t mask = (size_t)DK_SIZE(keys) - 1;
+ for (Py_ssize_t ix = 0; ix != n; ix++, ep++) {
+ Py_hash_t hash = ep->me_hash;
+ size_t i = hash & mask;
+ for (size_t perturb = hash; dk_get_index(keys, i) != DKIX_EMPTY;) {
+ perturb >>= PERTURB_SHIFT;
+ i = mask & ((i << 2) + i + perturb + 1);
+ }
+ dk_set_index(keys, i, ix);
}
- ep = &ep0[k->dk_nentries];
- assert(ep->me_value == NULL);
- dk_set_index(k, i, k->dk_nentries);
- k->dk_nentries++;
- ep->me_key = key;
- ep->me_hash = hash;
- ep->me_value = value;
}
/*
@@ -1248,10 +1228,10 @@ but can be resplit by make_keys_shared().
static int
dictresize(PyDictObject *mp, Py_ssize_t minused)
{
- Py_ssize_t i, newsize;
+ Py_ssize_t newsize, numentries;
PyDictKeysObject *oldkeys;
PyObject **oldvalues;
- PyDictKeyEntry *ep0;
+ PyDictKeyEntry *oldentries, *newentries;
/* Find the smallest table size > minused. */
for (newsize = PyDict_MINSIZE;
@@ -1262,8 +1242,14 @@ dictresize(PyDictObject *mp, Py_ssize_t minused)
PyErr_NoMemory();
return -1;
}
+
oldkeys = mp->ma_keys;
- oldvalues = mp->ma_values;
+
+ /* NOTE: Current odict checks mp->ma_keys to detect resize happen.
+ * So we can't reuse oldkeys even if oldkeys->dk_size == newsize.
+ * TODO: Try reusing oldkeys when reimplement odict.
+ */
+
/* Allocate a new table. */
mp->ma_keys = new_keys_object(newsize);
if (mp->ma_keys == NULL) {
@@ -1272,42 +1258,59 @@ dictresize(PyDictObject *mp, Py_ssize_t minused)
}
if (oldkeys->dk_lookup == lookdict)
mp->ma_keys->dk_lookup = lookdict;
- mp->ma_values = NULL;
- ep0 = DK_ENTRIES(oldkeys);
- /* Main loop below assumes we can transfer refcount to new keys
- * and that value is stored in me_value.
- * Increment ref-counts and copy values here to compensate
- * This (resizing a split table) should be relatively rare */
+
+ numentries = mp->ma_used;
+ oldentries = DK_ENTRIES(oldkeys);
+ newentries = DK_ENTRIES(mp->ma_keys);
+ oldvalues = mp->ma_values;
if (oldvalues != NULL) {
- for (i = 0; i < oldkeys->dk_nentries; i++) {
- if (oldvalues[i] != NULL) {
- Py_INCREF(ep0[i].me_key);
- ep0[i].me_value = oldvalues[i];
- }
- }
- }
- /* Main loop */
- for (i = 0; i < oldkeys->dk_nentries; i++) {
- PyDictKeyEntry *ep = &ep0[i];
- if (ep->me_value != NULL) {
- insertdict_clean(mp, ep->me_key, ep->me_hash, ep->me_value);
+ /* Convert split table into new combined table.
+ * We must incref keys; we can transfer values.
+ * Note that values of split table is always dense.
+ */
+ for (Py_ssize_t i = 0; i < numentries; i++) {
+ assert(oldvalues[i] != NULL);
+ PyDictKeyEntry *ep = &oldentries[i];
+ PyObject *key = ep->me_key;
+ Py_INCREF(key);
+ newentries[i].me_key = key;
+ newentries[i].me_hash = ep->me_hash;
+ newentries[i].me_value = oldvalues[i];
}
- }
- mp->ma_keys->dk_usable -= mp->ma_used;
- if (oldvalues != NULL) {
- /* NULL out me_value slot in oldkeys, in case it was shared */
- for (i = 0; i < oldkeys->dk_nentries; i++)
- ep0[i].me_value = NULL;
+
DK_DECREF(oldkeys);
+ mp->ma_values = NULL;
if (oldvalues != empty_values) {
free_values(oldvalues);
}
}
- else {
+ else { // combined table.
+ if (oldkeys->dk_nentries == numentries) {
+ memcpy(newentries, oldentries, numentries * sizeof(PyDictKeyEntry));
+ }
+ else {
+ PyDictKeyEntry *ep = oldentries;
+ for (Py_ssize_t i = 0; i < numentries; i++) {
+ while (ep->me_value == NULL)
+ ep++;
+ newentries[i] = *ep++;
+ }
+ }
+
assert(oldkeys->dk_lookup != lookdict_split);
assert(oldkeys->dk_refcnt == 1);
- DK_DEBUG_DECREF PyObject_FREE(oldkeys);
+ if (oldkeys->dk_size == PyDict_MINSIZE &&
+ numfreekeys < PyDict_MAXFREELIST) {
+ DK_DEBUG_DECREF keys_free_list[numfreekeys++] = oldkeys;
+ }
+ else {
+ DK_DEBUG_DECREF PyObject_FREE(oldkeys);
+ }
}
+
+ build_indices(mp->ma_keys, newentries, numentries);
+ mp->ma_keys->dk_usable -= numentries;
+ mp->ma_keys->dk_nentries = numentries;
return 0;
}