Skip to content

Commit

Permalink
pythongh-111962: Make dtoa thread-safe in --disable-gil builds.
Browse files Browse the repository at this point in the history
This avoids using the Bigint free-list in `--disable-gil` builds,
and pre-computes the needed powers of 5 during interpreter initialization.
This makes two changes to dtoa
  • Loading branch information
colesbury committed Nov 13, 2023
1 parent babb787 commit 5ef22ab
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 30 deletions.
15 changes: 10 additions & 5 deletions Include/internal/pycore_dtoa.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,19 @@ struct _dtoa_state {
/* The size of the Bigint freelist */
#define Bigint_Kmax 7

/* The size of the cached powers of 5 array */
#define Bigint_Pow5max 7

#ifndef PRIVATE_MEM
#define PRIVATE_MEM 2304
#endif
#define Bigint_PREALLOC_SIZE \
((PRIVATE_MEM+sizeof(double)-1)/sizeof(double))

struct _dtoa_state {
/* p5s is a linked list of powers of 5 of the form 5**(2**i), i >= 2 */
/* p5s is an array of powers of 5 of the form 5**(2**i), i >= 2 */
struct Bigint *p5s[Bigint_Pow5max];
// XXX This should be freed during runtime fini.
struct Bigint *p5s;
struct Bigint *freelist[Bigint_Kmax+1];
double preallocated[Bigint_PREALLOC_SIZE];
double *preallocated_next;
Expand All @@ -57,16 +60,18 @@ struct _dtoa_state {
#endif // !Py_USING_MEMORY_DEBUGGER


/* These functions are used by modules compiled as C extension like math:
they must be exported. */

extern double _Py_dg_strtod(const char *str, char **ptr);
extern char* _Py_dg_dtoa(double d, int mode, int ndigits,
int *decpt, int *sign, char **rve);
extern void _Py_dg_freedtoa(char *s);

#endif // _PY_SHORT_FLOAT_REPR == 1


extern PyStatus _PyDtoa_Init(PyInterpreterState *interp);
extern void _PyDtoa_Fini(PyInterpreterState *interp);


#ifdef __cplusplus
}
#endif
Expand Down
71 changes: 46 additions & 25 deletions Python/dtoa.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ BCinfo {
// struct Bigint is defined in pycore_dtoa.h.
typedef struct Bigint Bigint;

#ifndef Py_USING_MEMORY_DEBUGGER
#if !defined(Py_NOGIL) && !defined(Py_USING_MEMORY_DEBUGGER)

/* Memory management: memory is allocated from, and returned to, Kmax+1 pools
of memory, where pool k (0 <= k <= Kmax) is for Bigints b with b->maxwds ==
Expand Down Expand Up @@ -428,7 +428,7 @@ Bfree(Bigint *v)
}
}

#endif /* Py_USING_MEMORY_DEBUGGER */
#endif /* !defined(Py_NOGIL) && !defined(Py_USING_MEMORY_DEBUGGER) */

#define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \
y->wds*sizeof(Long) + 2*sizeof(int))
Expand Down Expand Up @@ -673,7 +673,7 @@ mult(Bigint *a, Bigint *b)
static Bigint *
pow5mult(Bigint *b, int k)
{
Bigint *b1, *p5, *p51;
Bigint *b1, *p5, **p5s;
int i;
static const int p05[3] = { 5, 25, 125 };

Expand All @@ -685,19 +685,12 @@ pow5mult(Bigint *b, int k)

if (!(k >>= 2))
return b;
assert(k < (1 << (Bigint_Pow5max)));
PyInterpreterState *interp = _PyInterpreterState_GET();
p5 = interp->dtoa.p5s;
if (!p5) {
/* first time */
p5 = i2b(625);
if (p5 == NULL) {
Bfree(b);
return NULL;
}
interp->dtoa.p5s = p5;
p5->next = 0;
}
p5s = interp->dtoa.p5s;
for(;;) {
p5 = *p5s;
p5s++;
if (k & 1) {
b1 = mult(b, p5);
Bfree(b);
Expand All @@ -707,17 +700,6 @@ pow5mult(Bigint *b, int k)
}
if (!(k >>= 1))
break;
p51 = p5->next;
if (!p51) {
p51 = mult(p5,p5);
if (p51 == NULL) {
Bfree(b);
return NULL;
}
p51->next = 0;
p5->next = p51;
}
p5 = p51;
}
return b;
}
Expand Down Expand Up @@ -2811,3 +2793,42 @@ _Py_dg_dtoa(double dd, int mode, int ndigits,
}

#endif // _PY_SHORT_FLOAT_REPR == 1

PyStatus
_PyDtoa_Init(PyInterpreterState *interp)
{
#if _PY_SHORT_FLOAT_REPR == 1 && !defined(Py_USING_MEMORY_DEBUGGER)
Bigint **p5s = interp->dtoa.p5s;

// 5**4 = 625
Bigint *p5 = i2b(625);
if (p5 == NULL) {
return PyStatus_NoMemory();
}
p5s[0] = p5;

// compute 5**8, 5**16, 5**32, ..., 5**256
for (Py_ssize_t i = 1; i < Bigint_Pow5max; i++) {
p5 = mult(p5, p5);
if (p5 == NULL) {
return PyStatus_NoMemory();
}
p5s[i] = p5;
}

#endif
return PyStatus_Ok();
}

void
_PyDtoa_Fini(PyInterpreterState *interp)
{
#if _PY_SHORT_FLOAT_REPR == 1 && !defined(Py_USING_MEMORY_DEBUGGER)
Bigint **p5s = interp->dtoa.p5s;
for (Py_ssize_t i = 0; i < Bigint_Pow5max; i++) {
Bigint *p5 = p5s[i];
p5s[i] = NULL;
Bfree(p5);
}
#endif
}
6 changes: 6 additions & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,11 @@ pycore_interp_init(PyThreadState *tstate)
return status;
}

status = _PyDtoa_Init(interp);
if (_PyStatus_EXCEPTION(status)) {
return status;
}

// The GC must be initialized before the first GC collection.
status = _PyGC_Init(interp);
if (_PyStatus_EXCEPTION(status)) {
Expand Down Expand Up @@ -1781,6 +1786,7 @@ finalize_interp_clear(PyThreadState *tstate)
_PyXI_Fini(tstate->interp);
_PyExc_ClearExceptionGroupType(tstate->interp);
_Py_clear_generic_types(tstate->interp);
_PyDtoa_Fini(tstate->interp);

/* Clear interpreter state and all thread states */
_PyInterpreterState_Clear(tstate);
Expand Down

0 comments on commit 5ef22ab

Please sign in to comment.