Skip to content

Commit bb2ab1b

Browse files
committed
optimize context.run by making custom C Functions for it
1 parent 5910a18 commit bb2ab1b

File tree

3 files changed

+139
-4
lines changed

3 files changed

+139
-4
lines changed

uvloop/includes/compat.h

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,134 @@ int Context_Exit(PyObject *ctx) {
8585

8686
#endif
8787

88+
/* Originally apart of loop.pyx via calling context.run
89+
moved here so we can began optimizing more
90+
reason why something like this is more costly is because
91+
we have to find the .run method if these were C Functions instead
92+
This Call would no longer be needed and we skip right to the
93+
meat of the function (run) immediately however, we can go further
94+
to optimize that code too.
95+
96+
Before:
97+
cdef inline run_in_context1(context, method, arg):
98+
Py_INCREF(method)
99+
try:
100+
return context.run(method, arg)
101+
finally:
102+
Py_DECREF(method)
103+
104+
After:
105+
cdef inline run_in_context1(context, method, arg):
106+
Py_INCREF(method)
107+
try:
108+
return Context_RunNoArgs(context, method)
109+
finally:
110+
Py_DECREF(method)
111+
*/
112+
113+
/* Context.run is literally this code right here referenced from python 3.15.1
114+
115+
static PyObject *
116+
context_run(PyObject *self, PyObject *const *args,
117+
Py_ssize_t nargs, PyObject *kwnames)
118+
{
119+
PyThreadState *ts = _PyThreadState_GET();
120+
121+
if (nargs < 1) {
122+
_PyErr_SetString(ts, PyExc_TypeError,
123+
"run() missing 1 required positional argument");
124+
return NULL;
125+
}
126+
127+
if (_PyContext_Enter(ts, self)) {
128+
return NULL;
129+
}
130+
131+
PyObject *call_result = _PyObject_VectorcallTstate(
132+
ts, args[0], args + 1, nargs - 1, kwnames);
133+
134+
if (_PyContext_Exit(ts, self)) {
135+
Py_XDECREF(call_result);
136+
return NULL;
137+
}
138+
139+
return call_result;
140+
}
141+
142+
As we can see this code is not very expensive to maintain
143+
at all and can be simply reproduced and improved upon
144+
for our needs of being faster.
145+
146+
Will name them after the different object calls made
147+
to keep things less confusing.
148+
We also eliminate needing to find the run method by doing so.
149+
150+
These changes reflect recent addtions made to winloop.
151+
SEE: https://github.com/Vizonex/Winloop/pull/112
152+
*/
153+
154+
static PyObject* Context_RunNoArgs(PyObject* context, PyObject* method){
155+
/* NOTE: Were looking for -1 but we can also say it's not
156+
a no-zero value so we could even treat it as a true case... */
157+
if (Context_Enter(context)){
158+
return NULL;
159+
}
160+
161+
#if PY_VERSION_HEX >= 0x030a0000
162+
PyObject* call_result = PyObject_CallNoArgs(method);
163+
#else
164+
PyObject* call_result = PyObject_CallFunctionObjArgs(method, NULL);
165+
#endif
166+
167+
if (Context_Exit(context)){
168+
Py_XDECREF(call_result);
169+
return NULL;
170+
}
171+
172+
return call_result;
173+
}
174+
175+
static PyObject* Context_RunOneArg(PyObject* context, PyObject* method, PyObject* arg){
176+
if (Context_Enter(context)){
177+
return NULL;
178+
}
179+
/* Introduced in 3.9 */
180+
/* NOTE: Kept around backwards compatability since the same features are planned for uvloop */
181+
#if PY_VERSION_HEX >= 0x03090000
182+
PyObject* call_result = PyObject_CallOneArg(method, arg);
183+
#else /* verison < 3.9 */
184+
PyObject* call_result = PyObject_CallFunctionObjArgs(method, arg, NULL);
185+
#endif
186+
if (Context_Exit(context)){
187+
Py_XDECREF(call_result);
188+
return NULL;
189+
}
190+
return call_result;
191+
}
192+
193+
static PyObject* Context_RunTwoArgs(PyObject* context, PyObject* method, PyObject* arg1, PyObject* arg2){
194+
/* Cython can't really do this PyObject array packing so writing this in C
195+
has a really good advantage */
196+
if (Context_Enter(context)){
197+
return NULL;
198+
}
199+
#if PY_VERSION_HEX >= 0x03090000
200+
/* begin packing for call... */
201+
PyObject* args[2];
202+
args[0] = arg1;
203+
args[1] = arg2;
204+
PyObject* call_result = PyObject_Vectorcall(method, args, 2, NULL);
205+
#else
206+
PyObject* call_result = PyObject_CallFunctionObjArgs(method, arg1, arg2, NULL);
207+
#endif
208+
if (Context_Exit(context)){
209+
Py_XDECREF(call_result);
210+
return NULL;
211+
}
212+
return call_result;
213+
}
214+
215+
88216
/* inlined from cpython/Modules/signalmodule.c
89217
* https://github.com/python/cpython/blob/v3.13.0a6/Modules/signalmodule.c#L1931-L1951
90218
* private _Py_RestoreSignals has been moved to CPython internals in Python 3.13

uvloop/includes/python.pxd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ cdef extern from "includes/compat.h":
2323
object Context_CopyCurrent()
2424
int Context_Enter(object) except -1
2525
int Context_Exit(object) except -1
26+
27+
object Context_RunNoArgs(object context, object method)
28+
object Context_RunOneArg(object context, object method, object arg)
29+
object Context_RunTwoArgs(object context, object method, object arg1, object arg2)
2630

2731
void PyOS_BeforeFork()
2832
void PyOS_AfterFork_Parent()

uvloop/loop.pyx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ from .includes.python cimport (
1616
Context_CopyCurrent,
1717
Context_Enter,
1818
Context_Exit,
19+
Context_RunNoArgs,
20+
Context_RunOneArg,
21+
Context_RunTwoArgs
1922
PyMemoryView_FromMemory, PyBUF_WRITE,
2023
PyMemoryView_FromObject, PyMemoryView_Check,
2124
PyOS_AfterFork_Parent, PyOS_AfterFork_Child,
2225
PyOS_BeforeFork,
23-
PyUnicode_FromString
26+
PyUnicode_FromString,
2427
)
2528
from .includes.flowcontrol cimport add_flowcontrol_defaults
2629

@@ -98,23 +101,23 @@ cdef inline run_in_context(context, method):
98101
# See also: edgedb/edgedb#2222
99102
Py_INCREF(method)
100103
try:
101-
return context.run(method)
104+
return Context_RunNoArgs(context, method)
102105
finally:
103106
Py_DECREF(method)
104107

105108

106109
cdef inline run_in_context1(context, method, arg):
107110
Py_INCREF(method)
108111
try:
109-
return context.run(method, arg)
112+
return Context_RunOneArg(context, method, arg)
110113
finally:
111114
Py_DECREF(method)
112115

113116

114117
cdef inline run_in_context2(context, method, arg1, arg2):
115118
Py_INCREF(method)
116119
try:
117-
return context.run(method, arg1, arg2)
120+
return Context_RunTwoArgs(context, method, arg1, arg2)
118121
finally:
119122
Py_DECREF(method)
120123

0 commit comments

Comments
 (0)