Skip to content

Commit 48f98b7

Browse files
committed
Use unsafe PyList APIs for improved speed
In the case of the parse basecoro, we have full control over the gen->path object, which is a list, so we can directly call a few of its PyList_* unsafe functions instead of the safer, but more expensive PySequence_* ones. In the of builder, we also can safely assume builder->value_stack is a list, and when we access items we know we are within boundaries. One of the consequences of using PyList_GET_ITEM in particular in the parse basecoro is that we now deal with borrowed references, and therefore need to do less reference tracking. This in turned simplified the CONCAT macro to the point where it became unnecessary (and it already was a bit dirty to begin with). Local benchmarks show a speed increase of ~4% in the parse method across the different test cases. Signed-off-by: Rodrigo Tobar <rtobar@icrar.org>
1 parent e476bf8 commit 48f98b7

File tree

3 files changed

+19
-30
lines changed

3 files changed

+19
-30
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* Restructured source code so all code lives under `src/`,
2222
and the `ijson.backends._yajl2` extension under `src/ijson/backends/ext/_yajl2`.
2323
This allows C backend tests to actually run on cibuildwheel.
24+
* Improved performance of `parse` routine in C backend by ~4%.
2425
* Fixed several potential stability issues in C backend
2526
around correct error handling.
2627
* Pointing to our own fork of yajl (for when we build it ourselves)

src/ijson/backends/ext/_yajl2/builder.h

+4-5
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ int builder_reset(builder_t *builder)
112112
Py_CLEAR(builder->value);
113113
Py_CLEAR(builder->key);
114114

115-
Py_ssize_t nvals = PyList_Size(builder->value_stack);
115+
Py_ssize_t nvals = PyList_GET_SIZE(builder->value_stack);
116116
M1_M1(PyList_SetSlice(builder->value_stack, 0, nvals, NULL));
117117

118118
return 0;
@@ -121,14 +121,13 @@ int builder_reset(builder_t *builder)
121121
static inline
122122
int _builder_add(builder_t *builder, PyObject *value)
123123
{
124-
Py_ssize_t nvals = PyList_Size(builder->value_stack);
124+
Py_ssize_t nvals = PyList_GET_SIZE(builder->value_stack);
125125
if (nvals == 0) {
126126
Py_INCREF(value);
127127
builder->value = value;
128128
}
129129
else {
130-
PyObject *last;
131-
M1_N(last = PyList_GetItem(builder->value_stack, nvals-1));
130+
PyObject *last = PyList_GET_ITEM(builder->value_stack, nvals - 1);
132131
assert(("stack element not list or dict-like",
133132
PyList_Check(last) || PyMapping_Check(last)));
134133
if (PyList_Check(last)) {
@@ -182,7 +181,7 @@ int builder_event(builder_t *builder, enames_t enames, PyObject *ename, PyObject
182181
}
183182
else if (ename == enames.end_array_ename || ename == enames.end_map_ename) {
184183
// pop
185-
Py_ssize_t nvals = PyList_Size(builder->value_stack);
184+
Py_ssize_t nvals = PyList_GET_SIZE(builder->value_stack);
186185
M1_M1(PyList_SetSlice(builder->value_stack, nvals-1, nvals, NULL));
187186
}
188187
else {

src/ijson/backends/ext/_yajl2/parse_basecoro.c

+14-25
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,10 @@ static void parse_basecoro_dealloc(ParseBasecoro *self)
3939
Py_TYPE(self)->tp_free((PyObject*)self);
4040
}
4141

42-
#define CONCAT(tgt, first, second) \
43-
do { \
44-
tgt = PyUnicode_Concat(first, second); \
45-
Py_DECREF(first); \
46-
N_N(tgt); \
47-
} while(0);
48-
4942
PyObject* parse_basecoro_send_impl(PyObject *self, PyObject *event, PyObject *value)
5043
{
5144
ParseBasecoro *gen = (ParseBasecoro *)self;
52-
Py_ssize_t npaths = PyList_Size(gen->path);
45+
Py_ssize_t npaths = PyList_GET_SIZE(gen->path);
5346
enames_t enames = gen->module_state->enames;
5447
PyObject *dot = gen->module_state->dot;
5548
PyObject *dotitem = gen->module_state->dotitem;
@@ -61,48 +54,45 @@ PyObject* parse_basecoro_send_impl(PyObject *self, PyObject *event, PyObject *va
6154
// pop
6255
N_M1(PyList_SetSlice(gen->path, npaths - 1, npaths, NULL));
6356
npaths--;
64-
prefix = PySequence_GetItem(gen->path, npaths - 1);
57+
prefix = PyList_GET_ITEM(gen->path, npaths - 1);
6558
}
6659
else if (event == enames.map_key_ename) {
6760

6861
// last_path = path_stack[-2]
6962
// to_append = '.' + value if len(path_stack) > 1 else value
7063
// new_path = path_stack[-2] + to_append
71-
PyObject *last_path;
72-
N_N(last_path = PySequence_GetItem(gen->path, npaths - 2));
64+
PyObject *last_path = PyList_GET_ITEM(gen->path, npaths - 2);
65+
PyObject *last_path_dot = NULL;
7366
if (npaths > 2) {
74-
PyObject *last_path_dot;
75-
CONCAT(last_path_dot, last_path, dot);
67+
last_path_dot = PyUnicode_Concat(last_path, dot);
68+
N_N(last_path_dot);
7669
last_path = last_path_dot;
7770
}
78-
PyObject *new_path;
79-
CONCAT(new_path, last_path, value);
71+
PyObject *new_path = PyUnicode_Concat(last_path, value);
72+
N_N(new_path);
73+
Py_XDECREF(last_path_dot);
8074
PyList_SetItem(gen->path, npaths - 1, new_path);
8175

82-
prefix = PySequence_GetItem(gen->path, npaths - 2);
76+
prefix = PyList_GET_ITEM(gen->path, npaths - 2);
8377
}
8478
else {
85-
prefix = PySequence_GetItem(gen->path, npaths - 1);
79+
prefix = PyList_GET_ITEM(gen->path, npaths - 1);
8680
}
87-
N_N(prefix);
8881

8982
// If entering a map/array, append name to path
9083
if (event == enames.start_array_ename) {
9184

9285
// to_append = '.item' if path_stack[-1] else 'item'
9386
// path_stack.append(path_stack[-1] + to_append)
94-
PyObject *last_path;
95-
N_N(last_path = PySequence_GetItem(gen->path, npaths - 1));
96-
87+
PyObject *last_path = PyList_GET_ITEM(gen->path, npaths - 1);
9788
if (PyUnicode_GET_LENGTH(last_path) > 0) {
98-
PyObject *new_path;
99-
CONCAT(new_path, last_path, dotitem);
89+
PyObject *new_path = PyUnicode_Concat(last_path, dotitem);
90+
N_N(new_path);
10091
N_M1(PyList_Append(gen->path, new_path));
10192
Py_DECREF(new_path);
10293
}
10394
else {
10495
N_M1(PyList_Append(gen->path, item));
105-
Py_DECREF(last_path);
10696
}
10797
}
10898
else if (event == enames.start_map_ename) {
@@ -121,7 +111,6 @@ PyObject* parse_basecoro_send_impl(PyObject *self, PyObject *event, PyObject *va
121111
CORO_SEND(gen->target_send, res);
122112
Py_DECREF(res);
123113
}
124-
Py_DECREF(prefix);
125114
Py_RETURN_NONE;
126115
}
127116

0 commit comments

Comments
 (0)