From 64e47cd1ae5196fd15f49d22d531a6589d4a2f5b Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 26 Mar 2021 09:02:22 -0700 Subject: [PATCH 1/8] Adding PyGILState_Check() in object_api<>::operator(). --- include/pybind11/cast.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 0ad10e9a93..55e15a50b1 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1348,6 +1348,11 @@ unpacking_collector collect_arguments(Args &&...args) { template template object object_api::operator()(Args &&...args) const { +#if PY_VERSION_HEX >= 0x03040000 + if (!PyGILState_Check()) { + pybind11_fail("pybind11::object_api<>::operator() PyGILState_Check() failure."); + } +#endif return detail::collect_arguments(std::forward(args)...).call(derived().ptr()); } From 75cc8bd68d089ff5c6fe0ab7642cf0971a13e0f6 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 26 Mar 2021 10:50:13 -0700 Subject: [PATCH 2/8] 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 --- include/pybind11/cast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 55e15a50b1..b1abf36afc 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1348,7 +1348,7 @@ unpacking_collector collect_arguments(Args &&...args) { template template object object_api::operator()(Args &&...args) const { -#if PY_VERSION_HEX >= 0x03040000 +#if PY_VERSION_HEX >= 0x03060000 if (!PyGILState_Check()) { pybind11_fail("pybind11::object_api<>::operator() PyGILState_Check() failure."); } From 155ac1ab6bcfc8908d2fdabd0ee2c651a9c615e5 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 1 Apr 2021 13:57:01 -0700 Subject: [PATCH 3/8] Adding simple micro benchmark. --- include/pybind11/cast.h | 2 +- tests/test_callbacks.cpp | 6 ++++++ tests/test_callbacks.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index b1abf36afc..3225707406 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1348,7 +1348,7 @@ unpacking_collector collect_arguments(Args &&...args) { template template object object_api::operator()(Args &&...args) const { -#if PY_VERSION_HEX >= 0x03060000 +#if defined(NDEBUG) && PY_VERSION_HEX >= 0x03060000 if (!PyGILState_Check()) { pybind11_fail("pybind11::object_api<>::operator() PyGILState_Check() failure."); } diff --git a/tests/test_callbacks.cpp b/tests/test_callbacks.cpp index dffe538fc5..61bc3a8f06 100644 --- a/tests/test_callbacks.cpp +++ b/tests/test_callbacks.cpp @@ -172,4 +172,10 @@ TEST_SUBMODULE(callbacks, m) { for (auto i : work) start_f(py::cast(i)); }); + + m.def("callback_num_times", [](py::function f, std::size_t num) { + for (std::size_t i = 0; i < num; i++) { + f(); + } + }); } diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 039b877ced..7d115ab39c 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -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,30 @@ def test_async_async_callbacks(): t = Thread(target=test_async_callbacks) t.start() t.join() + + +def test_callback_num_times(capsys): + # Super-simple micro-benchmarking related to PR #2919. + one_million = 1000000 + num_millions = 20 # Try 20 for actual micro-benchmarking. + repeats = 10 # 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 + with capsys.disabled(): + 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" + % (num_millions, td, rate) + ) + if len(rates) > 1: + with capsys.disabled(): + print("Min Mean Max") + print( + "%6.3f %6.3f %6.3f" % (min(rates), sum(rates) / len(rates), max(rates)) + ) From f6b6160670cf56ddf66ce5d25972bbe57ef412ae Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 1 Apr 2021 14:10:14 -0700 Subject: [PATCH 4/8] Reducing test time to minimum (purely for coverage, not for accurate results). --- tests/test_callbacks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 7d115ab39c..cd1074a020 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -152,8 +152,8 @@ def test_async_async_callbacks(): def test_callback_num_times(capsys): # Super-simple micro-benchmarking related to PR #2919. one_million = 1000000 - num_millions = 20 # Try 20 for actual micro-benchmarking. - repeats = 10 # Try 10. + num_millions = 1 # Try 20 for actual micro-benchmarking. + repeats = 2 # Try 10. rates = [] for rep in range(repeats): t0 = time.time() From 02b8d23dd7ef7d9a18e87dea3a2b5dbe6e7f98c6 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 1 Apr 2021 16:39:50 -0700 Subject: [PATCH 5/8] Fixing silly oversight. --- include/pybind11/cast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 3225707406..f68a35bfa4 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1348,7 +1348,7 @@ unpacking_collector collect_arguments(Args &&...args) { template template object object_api::operator()(Args &&...args) const { -#if defined(NDEBUG) && PY_VERSION_HEX >= 0x03060000 +#if !defined(NDEBUG) && PY_VERSION_HEX >= 0x03060000 if (!PyGILState_Check()) { pybind11_fail("pybind11::object_api<>::operator() PyGILState_Check() failure."); } From ef5a01422a117114c1638bff08ab3fd83767ff98 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 1 Apr 2021 16:48:26 -0700 Subject: [PATCH 6/8] Minor code organization improvement in test. --- tests/test_callbacks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index cd1074a020..a4ce6b93f9 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -159,9 +159,9 @@ def test_callback_num_times(capsys): 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) with capsys.disabled(): - rate = num_millions / td if td else 0 - rates.append(rate) if not rep: print() print( From 682c31ac619a35b0bd1579bdfd4632305588906d Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 2 Apr 2021 12:44:14 -0700 Subject: [PATCH 7/8] Adding example runtimes. --- tests/test_callbacks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index a4ce6b93f9..76037e7355 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -151,6 +151,9 @@ def test_async_async_callbacks(): def test_callback_num_times(capsys): # 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. From 08e18c5f9e0554b3af960875203db7c2615d2341 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 2 Apr 2021 16:54:30 -0700 Subject: [PATCH 8/8] Removing capsys (just run with `-k test_callback_num_times -s` and using `.format()`. --- tests/test_callbacks.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 76037e7355..cec68bda5c 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -149,7 +149,7 @@ def test_async_async_callbacks(): t.join() -def test_callback_num_times(capsys): +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 @@ -164,16 +164,17 @@ def test_callback_num_times(capsys): td = time.time() - t0 rate = num_millions / td if td else 0 rates.append(rate) - with capsys.disabled(): - if not rep: - print() - print( - "callback_num_times: %d million / %.3f seconds = %.3f million / second" - % (num_millions, td, 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: - with capsys.disabled(): - print("Min Mean Max") - print( - "%6.3f %6.3f %6.3f" % (min(rates), sum(rates) / len(rates), max(rates)) + print("Min Mean Max") + print( + "{:6.3f} {:6.3f} {:6.3f}".format( + min(rates), sum(rates) / len(rates), max(rates) ) + )