Skip to content

Commit 410ba10

Browse files
committed
dtoa: make dtoa thread-safe
This adds a mutex around pow5mult to protect the global "p5s" cache of powers of 5. This also removes the non-thread-safe memory pools. We may want to use Ryu or Dragonbox in the future.
1 parent 0dddcb6 commit 410ba10

File tree

2 files changed

+12
-93
lines changed

2 files changed

+12
-93
lines changed

Include/internal/pycore_dtoa.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct _dtoa_runtime_state {
4343
struct _dtoa_runtime_state {
4444
/* p5s is a linked list of powers of 5 of the form 5**(2**i), i >= 2 */
4545
// XXX This should be freed during runtime fini.
46+
_PyMutex mutex;
4647
struct Bigint *p5s;
4748
struct Bigint *freelist[Bigint_Kmax+1];
4849
double preallocated[Bigint_PREALLOC_SIZE];

Python/dtoa.c

Lines changed: 11 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -318,90 +318,6 @@ BCinfo {
318318
// struct Bigint is defined in pycore_dtoa.h.
319319
typedef struct Bigint Bigint;
320320

321-
#ifndef Py_USING_MEMORY_DEBUGGER
322-
323-
/* Memory management: memory is allocated from, and returned to, Kmax+1 pools
324-
of memory, where pool k (0 <= k <= Kmax) is for Bigints b with b->maxwds ==
325-
1 << k. These pools are maintained as linked lists, with freelist[k]
326-
pointing to the head of the list for pool k.
327-
328-
On allocation, if there's no free slot in the appropriate pool, MALLOC is
329-
called to get more memory. This memory is not returned to the system until
330-
Python quits. There's also a private memory pool that's allocated from
331-
in preference to using MALLOC.
332-
333-
For Bigints with more than (1 << Kmax) digits (which implies at least 1233
334-
decimal digits), memory is directly allocated using MALLOC, and freed using
335-
FREE.
336-
337-
XXX: it would be easy to bypass this memory-management system and
338-
translate each call to Balloc into a call to PyMem_Malloc, and each
339-
Bfree to PyMem_Free. Investigate whether this has any significant
340-
performance on impact. */
341-
342-
#define freelist _PyRuntime.dtoa.freelist
343-
#define private_mem _PyRuntime.dtoa.preallocated
344-
#define pmem_next _PyRuntime.dtoa.preallocated_next
345-
346-
/* Allocate space for a Bigint with up to 1<<k digits */
347-
348-
static Bigint *
349-
Balloc(int k)
350-
{
351-
int x;
352-
Bigint *rv;
353-
unsigned int len;
354-
355-
if (k <= Bigint_Kmax && (rv = freelist[k]))
356-
freelist[k] = rv->next;
357-
else {
358-
x = 1 << k;
359-
len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1)
360-
/sizeof(double);
361-
if (k <= Bigint_Kmax &&
362-
pmem_next - private_mem + len <= (Py_ssize_t)Bigint_PREALLOC_SIZE
363-
) {
364-
rv = (Bigint*)pmem_next;
365-
pmem_next += len;
366-
}
367-
else {
368-
rv = (Bigint*)MALLOC(len*sizeof(double));
369-
if (rv == NULL)
370-
return NULL;
371-
}
372-
rv->k = k;
373-
rv->maxwds = x;
374-
}
375-
rv->sign = rv->wds = 0;
376-
return rv;
377-
}
378-
379-
/* Free a Bigint allocated with Balloc */
380-
381-
static void
382-
Bfree(Bigint *v)
383-
{
384-
if (v) {
385-
if (v->k > Bigint_Kmax)
386-
FREE((void*)v);
387-
else {
388-
v->next = freelist[v->k];
389-
freelist[v->k] = v;
390-
}
391-
}
392-
}
393-
394-
#undef pmem_next
395-
#undef private_mem
396-
#undef freelist
397-
398-
#else
399-
400-
/* Alternative versions of Balloc and Bfree that use PyMem_Malloc and
401-
PyMem_Free directly in place of the custom memory allocation scheme above.
402-
These are provided for the benefit of memory debugging tools like
403-
Valgrind. */
404-
405321
/* Allocate space for a Bigint with up to 1<<k digits */
406322

407323
static Bigint *
@@ -412,13 +328,10 @@ Balloc(int k)
412328
unsigned int len;
413329

414330
x = 1 << k;
415-
len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1)
416-
/sizeof(double);
417-
331+
len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1)/sizeof(double);
418332
rv = (Bigint*)MALLOC(len*sizeof(double));
419333
if (rv == NULL)
420334
return NULL;
421-
422335
rv->k = k;
423336
rv->maxwds = x;
424337
rv->sign = rv->wds = 0;
@@ -435,8 +348,6 @@ Bfree(Bigint *v)
435348
}
436349
}
437350

438-
#endif /* Py_USING_MEMORY_DEBUGGER */
439-
440351
#define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \
441352
y->wds*sizeof(Long) + 2*sizeof(int))
442353

@@ -692,13 +603,14 @@ pow5mult(Bigint *b, int k)
692603

693604
if (!(k >>= 2))
694605
return b;
606+
_PyMutex_lock(&_PyRuntime.dtoa.mutex);
695607
p5 = _PyRuntime.dtoa.p5s;
696608
if (!p5) {
697609
/* first time */
698610
p5 = i2b(625);
699611
if (p5 == NULL) {
700612
Bfree(b);
701-
return NULL;
613+
goto err;
702614
}
703615
_PyRuntime.dtoa.p5s = p5;
704616
p5->next = 0;
@@ -709,7 +621,7 @@ pow5mult(Bigint *b, int k)
709621
Bfree(b);
710622
b = b1;
711623
if (b == NULL)
712-
return NULL;
624+
goto err;
713625
}
714626
if (!(k >>= 1))
715627
break;
@@ -718,14 +630,20 @@ pow5mult(Bigint *b, int k)
718630
p51 = mult(p5,p5);
719631
if (p51 == NULL) {
720632
Bfree(b);
721-
return NULL;
633+
goto err;
722634
}
723635
p51->next = 0;
724636
p5->next = p51;
725637
}
726638
p5 = p51;
727639
}
640+
641+
_PyMutex_unlock(&_PyRuntime.dtoa.mutex);
728642
return b;
643+
644+
err:
645+
_PyMutex_unlock(&_PyRuntime.dtoa.mutex);
646+
return NULL;
729647
}
730648

731649
#else

0 commit comments

Comments
 (0)