Skip to content

Commit ddea208

Browse files
committed
Give Python a debug-mode pymalloc, much as sketched on Python-Dev.
When WITH_PYMALLOC is defined, define PYMALLOC_DEBUG to enable the debug allocator. This can be done independent of build type (release or debug). A debug build automatically defines PYMALLOC_DEBUG when pymalloc is enabled. It's a detected error to define PYMALLOC_DEBUG when pymalloc isn't enabled. Two debugging entry points defined only under PYMALLOC_DEBUG: + _PyMalloc_DebugCheckAddress(const void *p) can be used (e.g., from gdb) to sanity-check a memory block obtained from pymalloc. It sprays info to stderr (see next) and dies via Py_FatalError if the block is detectably damaged. + _PyMalloc_DebugDumpAddress(const void *p) can be used to spray info about a debug memory block to stderr. A tiny start at implementing "API family" checks isn't good for anything yet. _PyMalloc_DebugRealloc() has been optimized to do little when the new size is <= old size. However, if the new size is larger, it really can't call the underlying realloc() routine without either violating its contract, or knowing something non-trivial about how the underlying realloc() works. A memcpy is always done in this case. This was a disaster for (and only) one of the std tests: test_bufio creates single text file lines up to a million characters long. On Windows, fileobject.c's get_line() uses the horridly funky getline_via_fgets(), which keeps growing and growing a string object hoping to find a newline. It grew the string object 1000 bytes each time, so for a million-character string it took approximately forever (I gave up after a few minutes). So, also: fileobject.c, getline_via_fgets(): When a single line is outrageously long, grow the string object at a mildly exponential rate, instead of just 1000 bytes at a time. That's enough so that a debug-build test_bufio finishes in about 5 seconds on my Win98SE box. I'm curious to try this on Win2K, because it has very different memory behavior than Win9X, and test_bufio always took a factor of 10 longer to complete on Win2K. It *could* be that the endless reallocs were simply killing it on Win2K even in the release build.
1 parent 91cc17d commit ddea208

File tree

4 files changed

+346
-28
lines changed

4 files changed

+346
-28
lines changed

Include/Python.h

+9
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@
6161

6262
#include "pyport.h"
6363

64+
/* Debug-mode build with pymalloc implies PYMALLOC_DEBUG.
65+
* PYMALLOC_DEBUG is in error if pymalloc is not in use.
66+
*/
67+
#if defined(Py_DEBUG) && defined(WITH_PYMALLOC) && !defined(PYMALLOC_DEBUG)
68+
#define PYMALLOC_DEBUG
69+
#endif
70+
#if defined(PYMALLOC_DEBUG) && !defined(WITH_PYMALLOC)
71+
#error "PYMALLOC_DEBUG requires WITH_PYMALLOC"
72+
#endif
6473
#include "pymem.h"
6574

6675
#include "object.h"

Include/pymem.h

+17-3
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,34 @@ extern DL_IMPORT(void) PyMem_Free(void *);
8989
it is recommended to write the test explicitly in the code.
9090
Note that according to ANSI C, free(NULL) has no effect. */
9191

92-
92+
9393
/* pymalloc (private to the interpreter) */
9494
#ifdef WITH_PYMALLOC
9595
DL_IMPORT(void *) _PyMalloc_Malloc(size_t nbytes);
9696
DL_IMPORT(void *) _PyMalloc_Realloc(void *p, size_t nbytes);
9797
DL_IMPORT(void) _PyMalloc_Free(void *p);
98+
99+
#ifdef PYMALLOC_DEBUG
100+
DL_IMPORT(void *) _PyMalloc_DebugMalloc(size_t nbytes, int family);
101+
DL_IMPORT(void *) _PyMalloc_DebugRealloc(void *p, size_t nbytes, int family);
102+
DL_IMPORT(void) _PyMalloc_DebugFree(void *p, int family);
103+
DL_IMPORT(void) _PyMalloc_DebugDumpAddress(const void *p);
104+
DL_IMPORT(void) _PyMalloc_DebugCheckAddress(const void *p);
105+
#define _PyMalloc_MALLOC(N) _PyMalloc_DebugMalloc(N, 0)
106+
#define _PyMalloc_REALLOC(P, N) _PyMalloc_DebugRealloc(P, N, 0)
107+
#define _PyMalloc_FREE(P) _PyMalloc_DebugFree(P, 0)
108+
109+
#else /* WITH_PYMALLOC && ! PYMALLOC_DEBUG */
98110
#define _PyMalloc_MALLOC _PyMalloc_Malloc
99111
#define _PyMalloc_REALLOC _PyMalloc_Realloc
100112
#define _PyMalloc_FREE _PyMalloc_Free
101-
#else
113+
#endif
114+
115+
#else /* ! WITH_PYMALLOC */
102116
#define _PyMalloc_MALLOC PyMem_MALLOC
103117
#define _PyMalloc_REALLOC PyMem_REALLOC
104118
#define _PyMalloc_FREE PyMem_FREE
105-
#endif
119+
#endif /* WITH_PYMALLOC */
106120

107121

108122
#ifdef __cplusplus

Objects/fileobject.c

+5-8
Original file line numberDiff line numberDiff line change
@@ -772,20 +772,17 @@ getline_via_fgets(FILE *fp)
772772
* cautions about boosting that. 300 was chosen because the worst real-life
773773
* text-crunching job reported on Python-Dev was a mail-log crawler where over
774774
* half the lines were 254 chars.
775-
* INCBUFSIZE is the amount by which we grow the buffer, if MAXBUFSIZE isn't
776-
* enough. It doesn't much matter what this is set to: we only get here for
777-
* absurdly long lines anyway.
778775
*/
779776
#define INITBUFSIZE 100
780777
#define MAXBUFSIZE 300
781-
#define INCBUFSIZE 1000
782778
char* p; /* temp */
783779
char buf[MAXBUFSIZE];
784780
PyObject* v; /* the string object result */
785781
char* pvfree; /* address of next free slot */
786782
char* pvend; /* address one beyond last free slot */
787783
size_t nfree; /* # of free buffer slots; pvend-pvfree */
788784
size_t total_v_size; /* total # of slots in buffer */
785+
size_t increment; /* amount to increment the buffer */
789786

790787
/* Optimize for normal case: avoid _PyString_Resize if at all
791788
* possible via first reading into stack buffer "buf".
@@ -853,7 +850,7 @@ getline_via_fgets(FILE *fp)
853850
/* The stack buffer isn't big enough; malloc a string object and read
854851
* into its buffer.
855852
*/
856-
total_v_size = MAXBUFSIZE + INCBUFSIZE;
853+
total_v_size = MAXBUFSIZE << 1;
857854
v = PyString_FromStringAndSize((char*)NULL, (int)total_v_size);
858855
if (v == NULL)
859856
return v;
@@ -897,7 +894,8 @@ getline_via_fgets(FILE *fp)
897894
}
898895
/* expand buffer and try again */
899896
assert(*(pvend-1) == '\0');
900-
total_v_size += INCBUFSIZE;
897+
increment = total_v_size >> 2; /* mild exponential growth */
898+
total_v_size += increment;
901899
if (total_v_size > INT_MAX) {
902900
PyErr_SetString(PyExc_OverflowError,
903901
"line is longer than a Python string can hold");
@@ -907,14 +905,13 @@ getline_via_fgets(FILE *fp)
907905
if (_PyString_Resize(&v, (int)total_v_size) < 0)
908906
return NULL;
909907
/* overwrite the trailing null byte */
910-
pvfree = BUF(v) + (total_v_size - INCBUFSIZE - 1);
908+
pvfree = BUF(v) + (total_v_size - increment - 1);
911909
}
912910
if (BUF(v) + total_v_size != p)
913911
_PyString_Resize(&v, p - BUF(v));
914912
return v;
915913
#undef INITBUFSIZE
916914
#undef MAXBUFSIZE
917-
#undef INCBUFSIZE
918915
}
919916
#endif /* ifdef USE_FGETS_IN_GETLINE */
920917

0 commit comments

Comments
 (0)