Adding PyGILState_Check() in object_api<>::operator(). (#2919)

* Adding PyGILState_Check() in object_api<>::operator().

* Enabling PyGILState_Check() for Python >= 3.6 only.

Possibly, this explains why PyGILState_Check() cannot safely be used with Python 3.4 and 3.5:

https://github.com/python/cpython/pull/10267#issuecomment-434881587

* Adding simple micro benchmark.

* Reducing test time to minimum (purely for coverage, not for accurate results).

* Fixing silly oversight.

* Minor code organization improvement in test.

* Adding example runtimes.

* Removing capsys (just run with `-k test_callback_num_times -s` and using `.format()`.
This commit is contained in:
Ralf W. Grosse-Kunstleve 2021-04-02 18:17:12 -07:00 committed by GitHub
parent f676782bec
commit ad6bf5cd39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 43 additions and 0 deletions

View File

@ -1348,6 +1348,11 @@ unpacking_collector<policy> collect_arguments(Args &&...args) {
template <typename Derived>
template <return_value_policy policy, typename... Args>
object object_api<Derived>::operator()(Args &&...args) const {
#if !defined(NDEBUG) && PY_VERSION_HEX >= 0x03060000
if (!PyGILState_Check()) {
pybind11_fail("pybind11::object_api<>::operator() PyGILState_Check() failure.");
}
#endif
return detail::collect_arguments<policy>(std::forward<Args>(args)...).call(derived().ptr());
}

View File

@ -172,4 +172,10 @@ TEST_SUBMODULE(callbacks, m) {
for (auto i : work)
start_f(py::cast<int>(i));
});
m.def("callback_num_times", [](py::function f, std::size_t num) {
for (std::size_t i = 0; i < num; i++) {
f();
}
});
}

View File

@ -2,6 +2,7 @@
import pytest
from pybind11_tests import callbacks as m
from threading import Thread
import time
def test_callbacks():
@ -146,3 +147,34 @@ def test_async_async_callbacks():
t = Thread(target=test_async_callbacks)
t.start()
t.join()
def test_callback_num_times():
# Super-simple micro-benchmarking related to PR #2919.
# Example runtimes (Intel Xeon 2.2GHz, fully optimized):
# num_millions 1, repeats 2: 0.1 secs
# num_millions 20, repeats 10: 11.5 secs
one_million = 1000000
num_millions = 1 # Try 20 for actual micro-benchmarking.
repeats = 2 # Try 10.
rates = []
for rep in range(repeats):
t0 = time.time()
m.callback_num_times(lambda: None, num_millions * one_million)
td = time.time() - t0
rate = num_millions / td if td else 0
rates.append(rate)
if not rep:
print()
print(
"callback_num_times: {:d} million / {:.3f} seconds = {:.3f} million / second".format(
num_millions, td, rate
)
)
if len(rates) > 1:
print("Min Mean Max")
print(
"{:6.3f} {:6.3f} {:6.3f}".format(
min(rates), sum(rates) / len(rates), max(rates)
)
)