Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4344278

Browse files
committed
Dictionary optimizations:
* Factored constant structure references out of the inner loops for PyDict_Next(), dict_keys(), dict_values(), and dict_items(). Gave measurable speedups to each (the improvement varies depending on the sparseness of the dictionary being measured). * Added a freelist scheme styled after that for tuples. Saves around 80% of the calls to malloc and free. About 10% of the time, the previous dictionary was completely empty; in those cases, the dictionary initialization with memset() can be skipped.
1 parent 969d8c0 commit 4344278

1 file changed

Lines changed: 61 additions & 24 deletions

File tree

Objects/dictobject.c

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ show_counts(void)
152152
INIT_NONZERO_DICT_SLOTS(mp); \
153153
} while(0)
154154

155+
/* Dictionary reuse scheme to save calls to malloc, free, and memset */
156+
#define MAXFREEDICTS 80
157+
static PyDictObject *free_dicts[MAXFREEDICTS];
158+
static int num_free_dicts = 0;
159+
155160
PyObject *
156161
PyDict_New(void)
157162
{
@@ -164,10 +169,23 @@ PyDict_New(void)
164169
Py_AtExit(show_counts);
165170
#endif
166171
}
167-
mp = PyObject_GC_New(dictobject, &PyDict_Type);
168-
if (mp == NULL)
169-
return NULL;
170-
EMPTY_TO_MINSIZE(mp);
172+
if (num_free_dicts) {
173+
mp = free_dicts[--num_free_dicts];
174+
assert (mp != NULL);
175+
assert (mp->ob_type == &PyDict_Type);
176+
_Py_NewReference((PyObject *)mp);
177+
if (mp->ma_fill) {
178+
EMPTY_TO_MINSIZE(mp);
179+
}
180+
assert (mp->ma_used == 0);
181+
assert (mp->ma_table == mp->ma_smalltable);
182+
assert (mp->ma_mask == PyDict_MINSIZE - 1);
183+
} else {
184+
mp = PyObject_GC_New(dictobject, &PyDict_Type);
185+
if (mp == NULL)
186+
return NULL;
187+
EMPTY_TO_MINSIZE(mp);
188+
}
171189
mp->ma_lookup = lookdict_string;
172190
#ifdef SHOW_CONVERSION_COUNTS
173191
++created;
@@ -672,23 +690,25 @@ PyDict_Clear(PyObject *op)
672690
int
673691
PyDict_Next(PyObject *op, int *ppos, PyObject **pkey, PyObject **pvalue)
674692
{
675-
int i;
676-
register dictobject *mp;
693+
register int i, mask;
694+
register dictentry *ep;
695+
677696
if (!PyDict_Check(op))
678697
return 0;
679-
mp = (dictobject *)op;
680698
i = *ppos;
681699
if (i < 0)
682700
return 0;
683-
while (i <= mp->ma_mask && mp->ma_table[i].me_value == NULL)
701+
ep = ((dictobject *)op)->ma_table;
702+
mask = ((dictobject *)op)->ma_mask;
703+
while (i <= mask && ep[i].me_value == NULL)
684704
i++;
685705
*ppos = i+1;
686-
if (i > mp->ma_mask)
706+
if (i > mask)
687707
return 0;
688708
if (pkey)
689-
*pkey = mp->ma_table[i].me_key;
709+
*pkey = ep[i].me_key;
690710
if (pvalue)
691-
*pvalue = mp->ma_table[i].me_value;
711+
*pvalue = ep[i].me_value;
692712
return 1;
693713
}
694714

@@ -710,7 +730,10 @@ dict_dealloc(register dictobject *mp)
710730
}
711731
if (mp->ma_table != mp->ma_smalltable)
712732
PyMem_DEL(mp->ma_table);
713-
mp->ob_type->tp_free((PyObject *)mp);
733+
if (num_free_dicts < MAXFREEDICTS && mp->ob_type == &PyDict_Type)
734+
free_dicts[num_free_dicts++] = mp;
735+
else
736+
mp->ob_type->tp_free((PyObject *)mp);
714737
Py_TRASHCAN_SAFE_END(mp)
715738
}
716739

@@ -882,7 +905,9 @@ static PyObject *
882905
dict_keys(register dictobject *mp)
883906
{
884907
register PyObject *v;
885-
register int i, j, n;
908+
register int i, j;
909+
dictentry *ep;
910+
int mask, n;
886911

887912
again:
888913
n = mp->ma_used;
@@ -896,22 +921,27 @@ dict_keys(register dictobject *mp)
896921
Py_DECREF(v);
897922
goto again;
898923
}
899-
for (i = 0, j = 0; i <= mp->ma_mask; i++) {
900-
if (mp->ma_table[i].me_value != NULL) {
901-
PyObject *key = mp->ma_table[i].me_key;
924+
ep = mp->ma_table;
925+
mask = mp->ma_mask;
926+
for (i = 0, j = 0; i <= mask; i++) {
927+
if (ep[i].me_value != NULL) {
928+
PyObject *key = ep[i].me_key;
902929
Py_INCREF(key);
903930
PyList_SET_ITEM(v, j, key);
904931
j++;
905932
}
906933
}
934+
assert(j == n);
907935
return v;
908936
}
909937

910938
static PyObject *
911939
dict_values(register dictobject *mp)
912940
{
913941
register PyObject *v;
914-
register int i, j, n;
942+
register int i, j;
943+
dictentry *ep;
944+
int mask, n;
915945

916946
again:
917947
n = mp->ma_used;
@@ -925,14 +955,17 @@ dict_values(register dictobject *mp)
925955
Py_DECREF(v);
926956
goto again;
927957
}
928-
for (i = 0, j = 0; i <= mp->ma_mask; i++) {
929-
if (mp->ma_table[i].me_value != NULL) {
930-
PyObject *value = mp->ma_table[i].me_value;
958+
ep = mp->ma_table;
959+
mask = mp->ma_mask;
960+
for (i = 0, j = 0; i <= mask; i++) {
961+
if (ep[i].me_value != NULL) {
962+
PyObject *value = ep[i].me_value;
931963
Py_INCREF(value);
932964
PyList_SET_ITEM(v, j, value);
933965
j++;
934966
}
935967
}
968+
assert(j == n);
936969
return v;
937970
}
938971

@@ -941,7 +974,9 @@ dict_items(register dictobject *mp)
941974
{
942975
register PyObject *v;
943976
register int i, j, n;
977+
int mask;
944978
PyObject *item, *key, *value;
979+
dictentry *ep;
945980

946981
/* Preallocate the list of tuples, to avoid allocations during
947982
* the loop over the items, which could trigger GC, which
@@ -968,10 +1003,12 @@ dict_items(register dictobject *mp)
9681003
goto again;
9691004
}
9701005
/* Nothing we do below makes any function calls. */
971-
for (i = 0, j = 0; i <= mp->ma_mask; i++) {
972-
if (mp->ma_table[i].me_value != NULL) {
973-
key = mp->ma_table[i].me_key;
974-
value = mp->ma_table[i].me_value;
1006+
ep = mp->ma_table;
1007+
mask = mp->ma_mask;
1008+
for (i = 0, j = 0; i <= mask; i++) {
1009+
if (ep[i].me_value != NULL) {
1010+
key = ep[i].me_key;
1011+
value = ep[i].me_value;
9751012
item = PyList_GET_ITEM(v, j);
9761013
Py_INCREF(key);
9771014
PyTuple_SET_ITEM(item, 0, key);

0 commit comments

Comments
 (0)