Skip to content

Commit b2c23f1

Browse files
committed
Use stack buffers for repr-mode float and complex formatting
float_repr() and complex_repr() now format into stack buffers via _Py_double_repr_buffered() and call _PyUnicode_FromASCII() directly, eliminating the per-call PyMem_Malloc/strlen/PyMem_Free round-trip (and PyUnicode_FromFormat for complex). float.__format__ and complex.__format__ short-circuit an empty format spec to the corresponding repr for exact instances, skipping the _PyUnicodeWriter setup. format_float_internal() in unicode_formatter.c uses a stack buffer when the effective format code is 'r' (no type code given), avoiding the PyOS_double_to_string heap allocation for f"{x:>10}" and similar. The write-only float_type local is removed; the equivalent dead re_float_type/im_float_type in format_complex_internal are removed while here. The legacy _PY_SHORT_FLOAT_REPR == 0 paths are preserved under #else for non-IEEE-754 platforms.
1 parent 3cb40da commit b2c23f1

3 files changed

Lines changed: 80 additions & 13 deletions

File tree

Objects/complexobject.c

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "pycore_long.h" // _PyLong_GetZero()
1313
#include "pycore_object.h" // _PyObject_Init()
1414
#include "pycore_pymath.h" // _Py_ADJUST_ERANGE2()
15+
#include "pycore_ryu.h" // _Py_double_repr_buffered()
1516

1617

1718
#define _PyComplexObject_CAST(op) ((PyComplexObject *)(op))
@@ -573,10 +574,38 @@ PyComplex_AsCComplex(PyObject *op)
573574
static PyObject *
574575
complex_repr(PyObject *op)
575576
{
577+
PyComplexObject *v = _PyComplexObject_CAST(op);
578+
#if _PY_SHORT_FLOAT_REPR == 1
579+
char buf[2 * _Py_DOUBLE_REPR_BUFSIZE + 4];
580+
char *p = buf;
581+
Py_ssize_t n;
582+
583+
if (v->cval.real == 0. && copysign(1.0, v->cval.real) == 1.0) {
584+
/* Real part is +0: just output the imaginary part and do not
585+
include parens. */
586+
n = _Py_double_repr_buffered(v->cval.imag, p, sizeof(buf), 0);
587+
p += n;
588+
*p++ = 'j';
589+
}
590+
else {
591+
/* Format imaginary part with sign, real part without. Include
592+
parens in the result. */
593+
*p++ = '(';
594+
n = _Py_double_repr_buffered(v->cval.real, p,
595+
_Py_DOUBLE_REPR_BUFSIZE, 0);
596+
p += n;
597+
n = _Py_double_repr_buffered(v->cval.imag, p,
598+
_Py_DOUBLE_REPR_BUFSIZE,
599+
Py_DTSF_SIGN);
600+
p += n;
601+
*p++ = 'j';
602+
*p++ = ')';
603+
}
604+
return _PyUnicode_FromASCII(buf, p - buf);
605+
#else
576606
int precision = 0;
577607
char format_code = 'r';
578608
PyObject *result = NULL;
579-
PyComplexObject *v = _PyComplexObject_CAST(op);
580609

581610
/* If these are non-NULL, they'll need to be freed. */
582611
char *pre = NULL;
@@ -625,6 +654,7 @@ complex_repr(PyObject *op)
625654
PyMem_Free(pre);
626655

627656
return result;
657+
#endif
628658
}
629659

630660
static Py_hash_t
@@ -913,6 +943,13 @@ complex___format___impl(PyComplexObject *self, PyObject *format_spec)
913943
{
914944
_PyUnicodeWriter writer;
915945
int ret;
946+
947+
if (PyUnicode_GET_LENGTH(format_spec) == 0
948+
&& Py_IS_TYPE(self, &PyComplex_Type))
949+
{
950+
return complex_repr((PyObject *)self);
951+
}
952+
916953
_PyUnicodeWriter_Init(&writer);
917954
ret = _PyComplex_FormatAdvancedWriter(
918955
&writer,

Objects/floatobject.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
1414
#include "pycore_object.h" // _PyObject_Init(), _PyDebugAllocatorStats()
1515
#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR
16+
#include "pycore_ryu.h" // _Py_double_repr_buffered()
1617
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1718
#include "pycore_stackref.h" // PyStackRef_AsPyObjectBorrow()
1819
#include "pycore_structseq.h" // _PyStructSequence_FiniBuiltin()
@@ -339,6 +340,13 @@ static PyObject *
339340
float_repr(PyObject *op)
340341
{
341342
PyFloatObject *v = _PyFloat_CAST(op);
343+
#if _PY_SHORT_FLOAT_REPR == 1
344+
char buf[_Py_DOUBLE_REPR_BUFSIZE];
345+
Py_ssize_t len = _Py_double_repr_buffered(PyFloat_AS_DOUBLE(v),
346+
buf, sizeof(buf),
347+
Py_DTSF_ADD_DOT_0);
348+
return _PyUnicode_FromASCII(buf, len);
349+
#else
342350
PyObject *result;
343351
char *buf;
344352

@@ -351,6 +359,7 @@ float_repr(PyObject *op)
351359
result = _PyUnicode_FromASCII(buf, strlen(buf));
352360
PyMem_Free(buf);
353361
return result;
362+
#endif
354363
}
355364

356365
/* Comparison is pretty much a nightmare. When comparing float to float,
@@ -1729,6 +1738,12 @@ float___format___impl(PyObject *self, PyObject *format_spec)
17291738
_PyUnicodeWriter writer;
17301739
int ret;
17311740

1741+
if (PyUnicode_GET_LENGTH(format_spec) == 0
1742+
&& Py_IS_TYPE(self, &PyFloat_Type))
1743+
{
1744+
return float_repr(self);
1745+
}
1746+
17321747
_PyUnicodeWriter_Init(&writer);
17331748
ret = _PyFloat_FormatAdvancedWriter(
17341749
&writer,

Objects/unicode_formatter.c

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "Python.h"
66
#include "pycore_fileutils.h" // _Py_GetLocaleconvNumeric()
77
#include "pycore_long.h" // _PyLong_FormatWriter()
8+
#include "pycore_ryu.h" // _Py_double_repr_buffered()
89
#include "pycore_unicodeobject.h" // PyUnicode_MAX_CHAR_VALUE()
910
#include <locale.h>
1011

@@ -1375,6 +1376,7 @@ format_float_internal(PyObject *value,
13751376
_PyUnicodeWriter *writer)
13761377
{
13771378
char *buf = NULL; /* buffer returned from PyOS_double_to_string */
1379+
int buf_allocated = 1;
13781380
Py_ssize_t n_digits;
13791381
Py_ssize_t n_remainder;
13801382
Py_ssize_t n_frac;
@@ -1390,7 +1392,6 @@ format_float_internal(PyObject *value,
13901392
int result = -1;
13911393
Py_UCS4 maxchar = 127;
13921394
Py_UCS4 sign_char = '\0';
1393-
int float_type; /* Used to see if we have a nan, inf, or regular float. */
13941395
PyObject *unicode_tmp = NULL;
13951396

13961397
/* Locale settings, either from the actual locale or
@@ -1440,11 +1441,23 @@ format_float_internal(PyObject *value,
14401441
/* Cast "type", because if we're in unicode we need to pass an
14411442
8-bit char. This is safe, because we've restricted what "type"
14421443
can be. */
1443-
buf = PyOS_double_to_string(val, (char)type, precision, flags,
1444-
&float_type);
1445-
if (buf == NULL)
1446-
goto done;
1447-
n_digits = strlen(buf);
1444+
#if _PY_SHORT_FLOAT_REPR == 1
1445+
char repr_buf[_Py_DOUBLE_REPR_BUFSIZE];
1446+
if (type == 'r') {
1447+
n_digits = _Py_double_repr_buffered(val, repr_buf, sizeof(repr_buf),
1448+
flags);
1449+
buf = repr_buf;
1450+
buf_allocated = 0;
1451+
}
1452+
else
1453+
#endif
1454+
{
1455+
buf = PyOS_double_to_string(val, (char)type, precision, flags,
1456+
NULL);
1457+
if (buf == NULL)
1458+
goto done;
1459+
n_digits = strlen(buf);
1460+
}
14481461

14491462
if (add_pct) {
14501463
/* We know that buf has a trailing zero (since we just called
@@ -1462,14 +1475,18 @@ format_float_internal(PyObject *value,
14621475
{
14631476
/* Fast path */
14641477
result = _PyUnicodeWriter_WriteASCIIString(writer, buf, n_digits);
1465-
PyMem_Free(buf);
1478+
if (buf_allocated) {
1479+
PyMem_Free(buf);
1480+
}
14661481
return result;
14671482
}
14681483

14691484
/* Since there is no unicode version of PyOS_double_to_string,
14701485
just use the 8 bit version and then convert to unicode. */
14711486
unicode_tmp = _PyUnicode_FromASCII(buf, n_digits);
1472-
PyMem_Free(buf);
1487+
if (buf_allocated) {
1488+
PyMem_Free(buf);
1489+
}
14731490
if (unicode_tmp == NULL)
14741491
goto done;
14751492

@@ -1556,8 +1573,6 @@ format_complex_internal(PyObject *value,
15561573
void *rdata;
15571574
Py_UCS4 re_sign_char = '\0';
15581575
Py_UCS4 im_sign_char = '\0';
1559-
int re_float_type; /* Used to see if we have a nan, inf, or regular float. */
1560-
int im_float_type;
15611576
int add_parens = 0;
15621577
int skip_re = 0;
15631578
Py_ssize_t lpad;
@@ -1628,11 +1643,11 @@ format_complex_internal(PyObject *value,
16281643
8-bit char. This is safe, because we've restricted what "type"
16291644
can be. */
16301645
re_buf = PyOS_double_to_string(re, (char)type, precision, flags,
1631-
&re_float_type);
1646+
NULL);
16321647
if (re_buf == NULL)
16331648
goto done;
16341649
im_buf = PyOS_double_to_string(im, (char)type, precision, flags,
1635-
&im_float_type);
1650+
NULL);
16361651
if (im_buf == NULL)
16371652
goto done;
16381653

0 commit comments

Comments
 (0)