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

Skip to content

Commit 5ce78f8

Browse files
committed
Patch by Charles G Waldman to avoid a sneaky memory leak in
_PyTuple_Resize(). In addition, a change suggested by Jeremy Hylton to limit the size of the free lists is also merged into this patch. Charles wrote initially: """ Test Case: run the following code: class Nothing: def __len__(self): return 5 def __getitem__(self, i): if i < 3: return i else: raise IndexError, i def g(a,*b,**c): return for x in xrange(1000000): g(*Nothing()) and watch Python's memory use go up and up. Diagnosis: The analysis begins with the call to PySequence_Tuple at line 1641 in ceval.c - the argument to g is seen to be a sequence but not a tuple, so it needs to be converted from an abstract sequence to a concrete tuple. PySequence_Tuple starts off by creating a new tuple of length 5 (line 1122 in abstract.c). Then at line 1149, since only 3 elements were assigned, _PyTuple_Resize is called to make the 5-tuple into a 3-tuple. When we're all done the 3-tuple is decrefed, but rather than being freed it is placed on the free_tuples cache. The basic problem is that the 3-tuples are being added to the cache but never picked up again, since _PyTuple_Resize doesn't make use of the free_tuples cache. If you are resizing a 5-tuple to a 3-tuple and there is already a 3-tuple in free_tuples[3], instead of using this tuple, _PyTuple_Resize will realloc the 5-tuple to a 3-tuple. It would more efficient to use the existing 3-tuple and cache the 5-tuple. By making _PyTuple_Resize aware of the free_tuples (just as PyTuple_New), we not only save a few calls to realloc, but also prevent this misbehavior whereby tuples are being added to the free_tuples list but never properly "recycled". """ And later: """ This patch replaces my submission of Sun, 16 Apr and addresses Jeremy Hylton's suggestions that we also limit the size of the free tuple list. I chose 2000 as the maximum number of tuples of any particular size to save. There was also a problem with the previous version of this patch causing a core dump if Python was built with Py_TRACE_REFS. This is fixed in the below version of the patch, which uses tupledealloc instead of _Py_Dealloc. """
1 parent 8421968 commit 5ce78f8

1 file changed

Lines changed: 59 additions & 16 deletions

File tree

Objects/tupleobject.c

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,20 @@ PERFORMANCE OF THIS SOFTWARE.
3333

3434
#include "Python.h"
3535

36+
/* Speed optimization to avoid frequent malloc/free of small tuples */
3637
#ifndef MAXSAVESIZE
37-
#define MAXSAVESIZE 20
38+
#define MAXSAVESIZE 20 /* Largest tuple to save on free list */
39+
#endif
40+
#ifndef MAXSAVEDTUPLES
41+
#define MAXSAVEDTUPLES 2000 /* Maximum number of tuples of each size to save */
3842
#endif
3943

4044
#if MAXSAVESIZE > 0
41-
/* Entries 1 upto MAXSAVESIZE are free lists, entry 0 is the empty
45+
/* Entries 1 up to MAXSAVESIZE are free lists, entry 0 is the empty
4246
tuple () of which at most one instance will be allocated.
4347
*/
4448
static PyTupleObject *free_tuples[MAXSAVESIZE];
49+
static int num_free_tuples[MAXSAVESIZE];
4550
#endif
4651
#ifdef COUNT_ALLOCS
4752
int fast_tuple_allocs;
@@ -71,6 +76,7 @@ PyTuple_New(size)
7176
(op = free_tuples[size]) != NULL)
7277
{
7378
free_tuples[size] = (PyTupleObject *) op->ob_item[0];
79+
num_free_tuples[size]--;
7480
#ifdef COUNT_ALLOCS
7581
fast_tuple_allocs++;
7682
#endif
@@ -104,6 +110,7 @@ PyTuple_New(size)
104110
#if MAXSAVESIZE > 0
105111
if (size == 0) {
106112
free_tuples[0] = op;
113+
++num_free_tuples[0];
107114
Py_INCREF(op); /* extra INCREF so that this is never freed */
108115
}
109116
#endif
@@ -171,16 +178,17 @@ tupledealloc(op)
171178
register PyTupleObject *op;
172179
{
173180
register int i;
174-
181+
register int len = op->ob_size;
175182
Py_TRASHCAN_SAFE_BEGIN(op)
176-
if (op->ob_size > 0) {
177-
i = op->ob_size;
183+
if (len > 0) {
184+
i = len;
178185
while (--i >= 0)
179186
Py_XDECREF(op->ob_item[i]);
180187
#if MAXSAVESIZE > 0
181-
if (op->ob_size < MAXSAVESIZE) {
182-
op->ob_item[0] = (PyObject *) free_tuples[op->ob_size];
183-
free_tuples[op->ob_size] = op;
188+
if (len < MAXSAVESIZE && num_free_tuples[len] < MAXSAVEDTUPLES) {
189+
op->ob_item[0] = (PyObject *) free_tuples[len];
190+
num_free_tuples[len]++;
191+
free_tuples[len] = op;
184192
goto done; /* return */
185193
}
186194
#endif
@@ -469,14 +477,49 @@ _PyTuple_Resize(pv, newsize, last_is_sticky)
469477
Py_XDECREF(v->ob_item[i]);
470478
v->ob_item[i] = NULL;
471479
}
472-
sv = (PyTupleObject *)
473-
realloc((char *)v,
474-
sizeof(PyTupleObject) + newsize * sizeof(PyObject *));
475-
*pv = (PyObject *) sv;
476-
if (sv == NULL) {
477-
PyMem_DEL(v);
478-
PyErr_NoMemory();
479-
return -1;
480+
#if MAXSAVESIZE > 0
481+
if (newsize == 0 && free_tuples[0]) {
482+
num_free_tuples[0]--;
483+
sv = free_tuples[0];
484+
sv->ob_size = 0;
485+
Py_INCREF(sv);
486+
#ifdef COUNT_ALLOCS
487+
tuple_zero_allocs++;
488+
#endif
489+
tupledealloc(v);
490+
*pv = (PyObject*) sv;
491+
return 0;
492+
}
493+
if (0 < newsize && newsize < MAXSAVESIZE &&
494+
(sv = free_tuples[newsize]) != NULL)
495+
{
496+
free_tuples[newsize] = (PyTupleObject *) sv->ob_item[0];
497+
num_free_tuples[newsize]--;
498+
#ifdef COUNT_ALLOCS
499+
fast_tuple_allocs++;
500+
#endif
501+
#ifdef Py_TRACE_REFS
502+
sv->ob_type = &PyTuple_Type;
503+
#endif
504+
for (i = 0; i < newsize; ++i){
505+
sv->ob_item[i] = v->ob_item[i];
506+
v->ob_item[i] = NULL;
507+
}
508+
sv->ob_size = v->ob_size;
509+
tupledealloc(v);
510+
*pv = (PyObject *) sv;
511+
} else
512+
#endif
513+
{
514+
sv = (PyTupleObject *)
515+
realloc((char *)v,
516+
sizeof(PyTupleObject) + newsize * sizeof(PyObject *));
517+
*pv = (PyObject *) sv;
518+
if (sv == NULL) {
519+
PyMem_DEL(v);
520+
PyErr_NoMemory();
521+
return -1;
522+
}
480523
}
481524
_Py_NewReference((PyObject *)sv);
482525
for (i = sv->ob_size; i < newsize; i++)

0 commit comments

Comments
 (0)