Skip to content

Commit d3f0882

Browse files
committed
Issue #14744: Use the new _PyUnicodeWriter internal API to speed up str%args and str.format(args)
* Formatting string, int, float and complex use the _PyUnicodeWriter API. It avoids a temporary buffer in most cases. * Add _PyUnicodeWriter_WriteStr() to restore the PyAccu optimization: just keep a reference to the string if the output is only composed of one string * Disable overallocation when formatting the last argument of str%args and str.format(args) * Overallocation allocates at least 100 characters: add min_length attribute to the _PyUnicodeWriter structure * Add new private functions: _PyUnicode_FastCopyCharacters(), _PyUnicode_FastFill() and _PyUnicode_FromASCII() The speed up is around 20% in average.
1 parent a1b0c9f commit d3f0882

File tree

12 files changed

+875
-434
lines changed

12 files changed

+875
-434
lines changed

Include/complexobject.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@ PyAPI_FUNC(Py_complex) PyComplex_AsCComplex(PyObject *op);
6363
/* Format the object based on the format_spec, as defined in PEP 3101
6464
(Advanced String Formatting). */
6565
#ifndef Py_LIMITED_API
66-
PyAPI_FUNC(PyObject *) _PyComplex_FormatAdvanced(PyObject *obj,
67-
PyObject *format_spec,
68-
Py_ssize_t start,
69-
Py_ssize_t end);
66+
PyAPI_FUNC(int) _PyComplex_FormatAdvancedWriter(
67+
_PyUnicodeWriter *writer,
68+
PyObject *obj,
69+
PyObject *format_spec,
70+
Py_ssize_t start,
71+
Py_ssize_t end);
7072
#endif
7173

7274
#ifdef __cplusplus

Include/floatobject.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,12 @@ PyAPI_FUNC(int) PyFloat_ClearFreeList(void);
112112

113113
/* Format the object based on the format_spec, as defined in PEP 3101
114114
(Advanced String Formatting). */
115-
PyAPI_FUNC(PyObject *) _PyFloat_FormatAdvanced(PyObject *obj,
116-
PyObject *format_spec,
117-
Py_ssize_t start,
118-
Py_ssize_t end);
115+
PyAPI_FUNC(int) _PyFloat_FormatAdvancedWriter(
116+
_PyUnicodeWriter *writer,
117+
PyObject *obj,
118+
PyObject *format_spec,
119+
Py_ssize_t start,
120+
Py_ssize_t end);
119121
#endif /* Py_LIMITED_API */
120122

121123
#ifdef __cplusplus

Include/longobject.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,22 @@ PyAPI_FUNC(int) _PyLong_AsByteArray(PyLongObject* v,
151151

152152
/* _PyLong_Format: Convert the long to a string object with given base,
153153
appending a base prefix of 0[box] if base is 2, 8 or 16. */
154-
PyAPI_FUNC(PyObject *) _PyLong_Format(PyObject *aa, int base);
154+
PyAPI_FUNC(PyObject *) _PyLong_Format(PyObject *obj, int base);
155+
156+
PyAPI_FUNC(int) _PyLong_FormatWriter(
157+
_PyUnicodeWriter *writer,
158+
PyObject *obj,
159+
int base,
160+
int alternate);
155161

156162
/* Format the object based on the format_spec, as defined in PEP 3101
157163
(Advanced String Formatting). */
158-
PyAPI_FUNC(PyObject *) _PyLong_FormatAdvanced(PyObject *obj,
159-
PyObject *format_spec,
160-
Py_ssize_t start,
161-
Py_ssize_t end);
164+
PyAPI_FUNC(int) _PyLong_FormatAdvancedWriter(
165+
_PyUnicodeWriter *writer,
166+
PyObject *obj,
167+
PyObject *format_spec,
168+
Py_ssize_t start,
169+
Py_ssize_t end);
162170
#endif /* Py_LIMITED_API */
163171

164172
/* These aren't really part of the long object, but they're handy. The

Include/unicodeobject.h

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -648,8 +648,20 @@ PyAPI_FUNC(Py_ssize_t) PyUnicode_CopyCharacters(
648648
Py_ssize_t from_start,
649649
Py_ssize_t how_many
650650
);
651+
652+
/* Unsafe version of PyUnicode_CopyCharacters(): don't check arguments and so
653+
may crash if parameters are invalid (e.g. if the output string
654+
is too short). */
655+
PyAPI_FUNC(void) _PyUnicode_FastCopyCharacters(
656+
PyObject *to,
657+
Py_ssize_t to_start,
658+
PyObject *from,
659+
Py_ssize_t from_start,
660+
Py_ssize_t how_many
661+
);
651662
#endif
652663

664+
#ifndef Py_LIMITED_API
653665
/* Fill a string with a character: write fill_char into
654666
unicode[start:start+length].
655667
@@ -658,13 +670,21 @@ PyAPI_FUNC(Py_ssize_t) PyUnicode_CopyCharacters(
658670
659671
Return the number of written character, or return -1 and raise an exception
660672
on error. */
661-
#ifndef Py_LIMITED_API
662673
PyAPI_FUNC(Py_ssize_t) PyUnicode_Fill(
663674
PyObject *unicode,
664675
Py_ssize_t start,
665676
Py_ssize_t length,
666677
Py_UCS4 fill_char
667678
);
679+
680+
/* Unsafe version of PyUnicode_Fill(): don't check arguments and so may crash
681+
if parameters are invalid (e.g. if length is longer than the string). */
682+
PyAPI_FUNC(void) _PyUnicode_FastFill(
683+
PyObject *unicode,
684+
Py_ssize_t start,
685+
Py_ssize_t length,
686+
Py_UCS4 fill_char
687+
);
668688
#endif
669689

670690
/* Create a Unicode Object from the Py_UNICODE buffer u of the given
@@ -696,13 +716,19 @@ PyAPI_FUNC(PyObject*) PyUnicode_FromString(
696716
const char *u /* UTF-8 encoded string */
697717
);
698718

719+
#ifndef Py_LIMITED_API
699720
/* Create a new string from a buffer of Py_UCS1, Py_UCS2 or Py_UCS4 characters.
700721
Scan the string to find the maximum character. */
701-
#ifndef Py_LIMITED_API
702722
PyAPI_FUNC(PyObject*) PyUnicode_FromKindAndData(
703723
int kind,
704724
const void *buffer,
705725
Py_ssize_t size);
726+
727+
/* Create a new string from a buffer of ASCII characters.
728+
WARNING: Don't check if the string contains any non-ASCII character. */
729+
PyAPI_FUNC(PyObject*) _PyUnicode_FromASCII(
730+
const char *buffer,
731+
Py_ssize_t size);
706732
#endif
707733

708734
PyAPI_FUNC(PyObject*) PyUnicode_Substring(
@@ -864,13 +890,70 @@ PyAPI_FUNC(PyObject *) PyUnicode_FromFormat(
864890
...
865891
);
866892

893+
#ifndef Py_LIMITED_API
894+
typedef struct {
895+
PyObject *buffer;
896+
void *data;
897+
enum PyUnicode_Kind kind;
898+
Py_UCS4 maxchar;
899+
Py_ssize_t size;
900+
Py_ssize_t pos;
901+
/* minimum length of the buffer when overallocation is enabled,
902+
see _PyUnicodeWriter_Init() */
903+
Py_ssize_t min_length;
904+
struct {
905+
unsigned char overallocate:1;
906+
/* If readonly is 1, buffer is a shared string (cannot be modified)
907+
and size is set to 0. */
908+
unsigned char readonly:1;
909+
} flags;
910+
} _PyUnicodeWriter ;
911+
912+
/* Initialize a Unicode writer.
913+
914+
If min_length is greater than zero, _PyUnicodeWriter_Prepare()
915+
overallocates the buffer and min_length is the minimum length in characters
916+
of the buffer. */
917+
PyAPI_FUNC(void)
918+
_PyUnicodeWriter_Init(_PyUnicodeWriter *writer, Py_ssize_t min_length);
919+
920+
/* Prepare the buffer to write 'length' characters
921+
with the specified maximum character.
922+
923+
Return 0 on success, raise an exception and return -1 on error. */
924+
#define _PyUnicodeWriter_Prepare(WRITER, LENGTH, MAXCHAR) \
925+
(((MAXCHAR) <= (WRITER)->maxchar \
926+
&& (LENGTH) <= (WRITER)->size - (WRITER)->pos) \
927+
? 0 \
928+
: (((LENGTH) == 0) \
929+
? 0 \
930+
: _PyUnicodeWriter_PrepareInternal((WRITER), (LENGTH), (MAXCHAR))))
931+
932+
/* Don't call this function directly, use the _PyUnicodeWriter_Prepare() macro
933+
instead. */
934+
PyAPI_FUNC(int)
935+
_PyUnicodeWriter_PrepareInternal(_PyUnicodeWriter *writer,
936+
Py_ssize_t length, Py_UCS4 maxchar);
937+
938+
PyAPI_FUNC(int)
939+
_PyUnicodeWriter_WriteStr(_PyUnicodeWriter *writer, PyObject *str);
940+
941+
PyAPI_FUNC(PyObject *)
942+
_PyUnicodeWriter_Finish(_PyUnicodeWriter *writer);
943+
944+
PyAPI_FUNC(void)
945+
_PyUnicodeWriter_Dealloc(_PyUnicodeWriter *writer);
946+
#endif
947+
867948
#ifndef Py_LIMITED_API
868949
/* Format the object based on the format_spec, as defined in PEP 3101
869950
(Advanced String Formatting). */
870-
PyAPI_FUNC(PyObject *) _PyUnicode_FormatAdvanced(PyObject *obj,
871-
PyObject *format_spec,
872-
Py_ssize_t start,
873-
Py_ssize_t end);
951+
PyAPI_FUNC(int) _PyUnicode_FormatAdvancedWriter(
952+
_PyUnicodeWriter *writer,
953+
PyObject *obj,
954+
PyObject *format_spec,
955+
Py_ssize_t start,
956+
Py_ssize_t end);
874957
#endif
875958

876959
PyAPI_FUNC(void) PyUnicode_InternInPlace(PyObject **);

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Core and Builtins
1313
- Issue #14835: Make plistlib output empty arrays & dicts like OS X.
1414
Patch by Sidney San Martín.
1515

16+
- Issue #14744: Use the new _PyUnicodeWriter internal API to speed up
17+
str%args and str.format(args).
18+
1619
- Issue #14930: Make memoryview objects weakrefable.
1720

1821
- Issue #14775: Fix a potential quadratic dict build-up due to the garbage

Objects/complexobject.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -699,11 +699,22 @@ static PyObject *
699699
complex__format__(PyObject* self, PyObject* args)
700700
{
701701
PyObject *format_spec;
702+
_PyUnicodeWriter writer;
703+
int ret;
702704

703705
if (!PyArg_ParseTuple(args, "U:__format__", &format_spec))
704-
return NULL;
705-
return _PyComplex_FormatAdvanced(self, format_spec, 0,
706-
PyUnicode_GET_LENGTH(format_spec));
706+
return NULL;
707+
708+
_PyUnicodeWriter_Init(&writer, 0);
709+
ret = _PyComplex_FormatAdvancedWriter(
710+
&writer,
711+
self,
712+
format_spec, 0, PyUnicode_GET_LENGTH(format_spec));
713+
if (ret == -1) {
714+
_PyUnicodeWriter_Dealloc(&writer);
715+
return NULL;
716+
}
717+
return _PyUnicodeWriter_Finish(&writer);
707718
}
708719

709720
#if 0

Objects/floatobject.c

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,15 @@ static PyObject *
267267
float_repr(PyFloatObject *v)
268268
{
269269
PyObject *result;
270-
char *buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(v),
271-
'r', 0,
272-
Py_DTSF_ADD_DOT_0,
273-
NULL);
270+
char *buf;
271+
272+
buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(v),
273+
'r', 0,
274+
Py_DTSF_ADD_DOT_0,
275+
NULL);
274276
if (!buf)
275277
return PyErr_NoMemory();
276-
result = PyUnicode_FromString(buf);
278+
result = _PyUnicode_FromASCII(buf, strlen(buf));
277279
PyMem_Free(buf);
278280
return result;
279281
}
@@ -1703,11 +1705,22 @@ static PyObject *
17031705
float__format__(PyObject *self, PyObject *args)
17041706
{
17051707
PyObject *format_spec;
1708+
_PyUnicodeWriter writer;
1709+
int ret;
17061710

17071711
if (!PyArg_ParseTuple(args, "U:__format__", &format_spec))
17081712
return NULL;
1709-
return _PyFloat_FormatAdvanced(self, format_spec, 0,
1710-
PyUnicode_GET_LENGTH(format_spec));
1713+
1714+
_PyUnicodeWriter_Init(&writer, 0);
1715+
ret = _PyFloat_FormatAdvancedWriter(
1716+
&writer,
1717+
self,
1718+
format_spec, 0, PyUnicode_GET_LENGTH(format_spec));
1719+
if (ret == -1) {
1720+
_PyUnicodeWriter_Dealloc(&writer);
1721+
return NULL;
1722+
}
1723+
return _PyUnicodeWriter_Finish(&writer);
17111724
}
17121725

17131726
PyDoc_STRVAR(float__format__doc,

0 commit comments

Comments
 (0)