-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-115754: Get singletons via function calls #116572
Conversation
vstinner
commented
Mar 10, 2024
•
edited by bedevere-app
bot
Loading
edited by bedevere-app
bot
- Issue: C API: Implement singletons as functions calls in the stable ABI 3.13 #115754
PR created to measure the impact on performance of getting the 5 singletons via function calls. |
Patch adding a benchmark function: diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index b6536045e6..03dcbd0cb9 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3284,6 +3284,39 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
_Py_COMP_DIAG_POP
}
+static PyObject *
+bench_none(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ Py_ssize_t loops;
+ if (!PyArg_ParseTuple(args, "n", &loops)) {
+ return NULL;
+ }
+
+ PyTime_t t1;
+ (void)PyTime_PerfCounter(&t1);
+ for (Py_ssize_t i=0; i < loops; i++) {
+ PyObject *obj;
+ int res = 0;
+
+ obj = Py_None;
+ res += Py_IsNone(obj) + Py_IsFalse(obj) + Py_IsTrue(obj);
+
+ obj = Py_False;
+ res += Py_IsNone(obj) + Py_IsFalse(obj) + Py_IsTrue(obj);
+
+ obj = Py_True;
+ res += Py_IsNone(obj) + Py_IsFalse(obj) + Py_IsTrue(obj);
+
+ if (res != 3) {
+ abort();
+ }
+ }
+ PyTime_t t2;
+ (void)PyTime_PerfCounter(&t2);
+
+ return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
@@ -3423,6 +3456,7 @@ static PyMethodDef TestMethods[] = {
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
{"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
+ {"bench_none", bench_none, METH_VARARGS},
{NULL, NULL} /* sentinel */
};
Benchmark script: import pyperf
import _testcapi
runner = pyperf.Runner()
runner.bench_time_func('bench', _testcapi.bench_none) Results of the microbenchmark with CPU isolation on Linux. Python built with -O3 without PGO nor LTO. PGO/LTO doesn't matter here, since _testcapi is built as a shared library: Py_None becomes a regular function call, there is nothing to optimize.
Converting |
I also ran pyperformance 1.11.0, this time with PGO+LTO and CPU isolation on Linux. My shell script run to run pyperformance:
Results.
|
In the limited C API version 3.13, getting Py_None, Py_False, Py_True, Py_Ellipsis and Py_NotImplemented singletons is now implemented as function calls at the stable ABI level to hide implementation details. Getting these constants still return borrowed references.
Is that benchmark using the macro Did you try with the single |
My microbenchmark used the macro. |
I didn't try. I dislike |
I think it's best to have limited API functions that do the checks inside of Python, so that callers who have an unknown object would pass it in and we would return a bool saying if it is/isn't that singleton (or in the Getting another instance of the singleton and running a normal comparison is like converting your Edit So what I'm saying is, hopefully the code that is calling the new getters is also calling the function comparisons, not the macros. I assume the benchmarks would look the same right now since the comparison is still cheap, though. |
Alternative: PR gh-116883 "Add Py_GetConstantRef() function". |
Apparently, this approach is preferred. I close this PR. |