Skip to content

Commit 4574e62

Browse files
committed
Fix massive slowdown in string formatting with str.format.
Example: ./python -m timeit -s "f='{}' + '-' * 1024 + '{}'; s='abcd' * 16384" "f.format(s, s)" -> before: 547 usec per loop -> after: 13 usec per loop -> 3.2: 22.5 usec per loop -> 2.7: 12.6 usec per loop
1 parent 5c0ba36 commit 4574e62

1 file changed

Lines changed: 24 additions & 128 deletions

File tree

Objects/stringlib/unicode_format.h

Lines changed: 24 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -114,87 +114,6 @@ autonumber_state_error(AutoNumberState state, int field_name_is_empty)
114114
/*********** Output string management functions ****************/
115115
/************************************************************************/
116116

117-
typedef struct {
118-
char *data;
119-
Py_UCS4 maxchar;
120-
unsigned int kind;
121-
Py_ssize_t pos, size;
122-
Py_ssize_t size_increment;
123-
} OutputString;
124-
125-
/* initialize an OutputString object, reserving size characters */
126-
static int
127-
output_initialize(OutputString *output, Py_ssize_t size)
128-
{
129-
output->data = PyMem_Malloc(size);
130-
if (output->data == NULL) {
131-
PyErr_NoMemory();
132-
return 0;
133-
}
134-
135-
output->maxchar = 127;
136-
output->kind = PyUnicode_1BYTE_KIND;
137-
output->pos = 0;
138-
output->size = size;
139-
output->size_increment = INITIAL_SIZE_INCREMENT;
140-
141-
return 1;
142-
}
143-
144-
/*
145-
output_extend reallocates the output string buffer.
146-
It returns a status: 0 for a failed reallocation,
147-
1 for success.
148-
*/
149-
150-
static int
151-
output_extend(OutputString *output, Py_ssize_t count)
152-
{
153-
Py_ssize_t maxlen = output->size + count + output->size_increment;
154-
155-
output->data = PyMem_Realloc(output->data, maxlen << (output->kind-1));
156-
output->size = maxlen;
157-
if (output->data == 0) {
158-
PyErr_NoMemory();
159-
return 0;
160-
}
161-
if (output->size_increment < MAX_SIZE_INCREMENT)
162-
output->size_increment *= SIZE_MULTIPLIER;
163-
return 1;
164-
}
165-
166-
static int
167-
output_widen(OutputString *output, Py_UCS4 maxchar)
168-
{
169-
int kind;
170-
void *data;
171-
Py_ssize_t i;
172-
if (maxchar <= output->maxchar)
173-
return 1;
174-
if (maxchar < 256) {
175-
output->maxchar = 255;
176-
return 1;
177-
}
178-
if (maxchar < 65536) {
179-
output->maxchar = 65535;
180-
kind = 2;
181-
}
182-
else {
183-
output->maxchar = 1<<21;
184-
kind = 3;
185-
}
186-
data = PyMem_Malloc(output->size << (kind-1));
187-
if (data == 0)
188-
return 0;
189-
for (i = 0; i < output->size; i++)
190-
PyUnicode_WRITE(kind, data, i,
191-
PyUnicode_READ(output->kind, output->data, i));
192-
PyMem_Free(output->data);
193-
output->data = data;
194-
output->kind = kind;
195-
return 1;
196-
}
197-
198117
/*
199118
output_data dumps characters into our output string
200119
buffer.
@@ -205,26 +124,17 @@ output_widen(OutputString *output, Py_UCS4 maxchar)
205124
1 for success.
206125
*/
207126
static int
208-
output_data(OutputString *output, PyObject *s, Py_ssize_t start, Py_ssize_t end)
127+
output_data(_PyAccu *acc, PyObject *s, Py_ssize_t start, Py_ssize_t end)
209128
{
210-
Py_ssize_t i;
211-
int kind;
212-
if ((output->pos + end - start > output->size) &&
213-
!output_extend(output, end - start))
129+
PyObject *substring;
130+
int r;
131+
132+
substring = PyUnicode_Substring(s, start, end);
133+
if (substring == NULL)
214134
return 0;
215-
kind = PyUnicode_KIND(s);
216-
if (PyUnicode_MAX_CHAR_VALUE(s) > output->maxchar) {
217-
Py_UCS4 maxchar = output->maxchar;
218-
for (i = start; i < end; i++)
219-
if (PyUnicode_READ(kind, PyUnicode_DATA(s), i) > maxchar)
220-
maxchar = PyUnicode_READ(kind, PyUnicode_DATA(s), i);
221-
if (!output_widen(output, maxchar))
222-
return 0;
223-
}
224-
for (i = start; i < end; i++)
225-
PyUnicode_WRITE(output->kind, output->data, output->pos++,
226-
PyUnicode_READ(kind, PyUnicode_DATA(s), i));
227-
return 1;
135+
r = _PyAccu_Accumulate(acc, substring);
136+
Py_DECREF(substring);
137+
return r == 0;
228138
}
229139

230140
/************************************************************************/
@@ -612,7 +522,7 @@ get_field_object(SubString *input, PyObject *args, PyObject *kwargs,
612522
appends to the output.
613523
*/
614524
static int
615-
render_field(PyObject *fieldobj, SubString *format_spec, OutputString *output)
525+
render_field(PyObject *fieldobj, SubString *format_spec, _PyAccu *acc)
616526
{
617527
int ok = 0;
618528
PyObject *result = NULL;
@@ -655,7 +565,7 @@ render_field(PyObject *fieldobj, SubString *format_spec, OutputString *output)
655565
goto done;
656566

657567
assert(PyUnicode_Check(result));
658-
ok = output_data(output, result, 0, PyUnicode_GET_LENGTH(result));
568+
ok = output_data(acc, result, 0, PyUnicode_GET_LENGTH(result));
659569
done:
660570
Py_XDECREF(format_spec_object);
661571
Py_XDECREF(result);
@@ -920,7 +830,7 @@ do_conversion(PyObject *obj, Py_UCS4 conversion)
920830
static int
921831
output_markup(SubString *field_name, SubString *format_spec,
922832
int format_spec_needs_expanding, Py_UCS4 conversion,
923-
OutputString *output, PyObject *args, PyObject *kwargs,
833+
_PyAccu *acc, PyObject *args, PyObject *kwargs,
924834
int recursion_depth, AutoNumber *auto_number)
925835
{
926836
PyObject *tmp = NULL;
@@ -961,7 +871,7 @@ output_markup(SubString *field_name, SubString *format_spec,
961871
else
962872
actual_format_spec = format_spec;
963873

964-
if (render_field(fieldobj, actual_format_spec, output) == 0)
874+
if (render_field(fieldobj, actual_format_spec, acc) == 0)
965875
goto done;
966876

967877
result = 1;
@@ -981,7 +891,7 @@ output_markup(SubString *field_name, SubString *format_spec,
981891
*/
982892
static int
983893
do_markup(SubString *input, PyObject *args, PyObject *kwargs,
984-
OutputString *output, int recursion_depth, AutoNumber *auto_number)
894+
_PyAccu *acc, int recursion_depth, AutoNumber *auto_number)
985895
{
986896
MarkupIterator iter;
987897
int format_spec_needs_expanding;
@@ -997,11 +907,11 @@ do_markup(SubString *input, PyObject *args, PyObject *kwargs,
997907
&field_name, &format_spec,
998908
&conversion,
999909
&format_spec_needs_expanding)) == 2) {
1000-
if (!output_data(output, literal.str, literal.start, literal.end))
910+
if (!output_data(acc, literal.str, literal.start, literal.end))
1001911
return 0;
1002912
if (field_present)
1003913
if (!output_markup(&field_name, &format_spec,
1004-
format_spec_needs_expanding, conversion, output,
914+
format_spec_needs_expanding, conversion, acc,
1005915
args, kwargs, recursion_depth, auto_number))
1006916
return 0;
1007917
}
@@ -1017,39 +927,25 @@ static PyObject *
1017927
build_string(SubString *input, PyObject *args, PyObject *kwargs,
1018928
int recursion_depth, AutoNumber *auto_number)
1019929
{
1020-
OutputString output;
1021-
PyObject *result = NULL;
1022-
1023-
output.data = NULL; /* needed so cleanup code always works */
930+
_PyAccu acc;
1024931

1025932
/* check the recursion level */
1026933
if (recursion_depth <= 0) {
1027934
PyErr_SetString(PyExc_ValueError,
1028935
"Max string recursion exceeded");
1029-
goto done;
936+
return NULL;
1030937
}
1031938

1032-
/* initial size is the length of the format string, plus the size
1033-
increment. seems like a reasonable default */
1034-
if (!output_initialize(&output,
1035-
input->end - input->start +
1036-
INITIAL_SIZE_INCREMENT))
1037-
goto done;
939+
if (_PyAccu_Init(&acc))
940+
return NULL;
1038941

1039-
if (!do_markup(input, args, kwargs, &output, recursion_depth,
942+
if (!do_markup(input, args, kwargs, &acc, recursion_depth,
1040943
auto_number)) {
1041-
goto done;
944+
_PyAccu_Destroy(&acc);
945+
return NULL;
1042946
}
1043947

1044-
result = PyUnicode_New(output.pos, output.maxchar);
1045-
if (!result)
1046-
goto done;
1047-
memcpy(PyUnicode_DATA(result), output.data, output.pos << (output.kind-1));
1048-
1049-
done:
1050-
if (output.data)
1051-
PyMem_Free(output.data);
1052-
return result;
948+
return _PyAccu_Finish(&acc);
1053949
}
1054950

1055951
/************************************************************************/

0 commit comments

Comments
 (0)