Skip to content

Commit

Permalink
Copy of files from pybind/pybind11#1895 (at commit pybind/pybind11@e5…
Browse files Browse the repository at this point in the history
  • Loading branch information
Ralf W. Grosse-Kunstleve committed May 18, 2022
1 parent 7b63b9f commit 7765113
Show file tree
Hide file tree
Showing 2 changed files with 322 additions and 0 deletions.
205 changes: 205 additions & 0 deletions test_perf_error_already_set.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#include "pybind11_tests.h"

namespace test_perf_error_already_set {

struct boost_python_error_already_set {
virtual ~boost_python_error_already_set() {}
};

void pure_unwind(std::size_t num_iterations) {
while (num_iterations) {
try {
throw boost_python_error_already_set();
} catch (const boost_python_error_already_set &) {
}
num_iterations--;
}
}

void generate_python_exception_with_traceback(const py::object &callable_raising_exception) {
try {
callable_raising_exception();
} catch (py::error_already_set &e) {
e.restore();
}
}

void do_real_work(std::size_t num_iterations) {
while (num_iterations) {
std::sqrt(static_cast<double>(num_iterations % 1000000));
num_iterations--;
}
}

void err_set_unwind_err_clear(const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
while (num_iterations) {
do_real_work(real_work);
try {
generate_python_exception_with_traceback(callable_raising_exception);
throw boost_python_error_already_set();
} catch (const boost_python_error_already_set &) {
if (call_error_string) {
py::detail::error_string();
}
PyErr_Clear();
}
num_iterations--;
}
}

void err_set_err_clear(const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
while (num_iterations) {
do_real_work(real_work);
generate_python_exception_with_traceback(callable_raising_exception);
if (call_error_string) {
py::detail::error_string();
}
PyErr_Clear();
num_iterations--;
}
}

void err_set_error_already_set(const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
while (num_iterations) {
do_real_work(real_work);
try {
generate_python_exception_with_traceback(callable_raising_exception);
throw py::error_already_set();
} catch (const py::error_already_set &e) {
if (call_error_string) {
e.what();
}
}
num_iterations--;
}
}

void err_set_err_fetch(const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
PyObject *exc_type, *exc_value, *exc_trace;
while (num_iterations) {
do_real_work(real_work);
generate_python_exception_with_traceback(callable_raising_exception);
PyErr_Fetch(&exc_type, &exc_value, &exc_trace);
if (call_error_string) {
py::detail::error_string(exc_type, exc_value, exc_trace);
}
num_iterations--;
}
}

void error_already_set_restore(const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
generate_python_exception_with_traceback(callable_raising_exception);
while (num_iterations) {
do_real_work(real_work);
try {
throw py::error_already_set();
} catch (py::error_already_set &e) {
if (call_error_string) {
e.what();
}
e.restore();
}
num_iterations--;
}
PyErr_Clear();
}

void err_fetch_err_restore(const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
generate_python_exception_with_traceback(callable_raising_exception);
PyObject *exc_type, *exc_value, *exc_trace;
while (num_iterations) {
do_real_work(real_work);
PyErr_Fetch(&exc_type, &exc_value, &exc_trace);
if (call_error_string) {
py::detail::error_string(exc_type, exc_value, exc_trace);
}
PyErr_Restore(exc_type, exc_value, exc_trace);
num_iterations--;
}
PyErr_Clear();
}

// https://github.com/pybind/pybind11/pull/1895 original PR description.
py::int_ pr1895_original_foo() {
py::dict d;
try {
return d["foo"];
} catch (const py::error_already_set &) {
return py::int_(42);
}
}

} // namespace test_perf_error_already_set

TEST_SUBMODULE(perf_error_already_set, m) {
using namespace test_perf_error_already_set;
m.def("pure_unwind", pure_unwind);
m.def("err_set_unwind_err_clear",
// Is there an easier way to get an exception with traceback?
[m](const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
err_set_unwind_err_clear(
callable_raising_exception, num_iterations, call_error_string, real_work);
});
m.def("err_set_err_clear",
[m](const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
err_set_err_clear(
callable_raising_exception, num_iterations, call_error_string, real_work);
});
m.def("err_set_error_already_set",
[m](const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
err_set_error_already_set(
callable_raising_exception, num_iterations, call_error_string, real_work);
});
m.def("err_set_err_fetch",
[m](const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
err_set_err_fetch(
callable_raising_exception, num_iterations, call_error_string, real_work);
});
m.def("error_already_set_restore",
[m](const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
error_already_set_restore(
callable_raising_exception, num_iterations, call_error_string, real_work);
});
m.def("err_fetch_err_restore",
[m](const py::object &callable_raising_exception,
std::size_t num_iterations,
bool call_error_string,
std::size_t real_work) {
err_fetch_err_restore(
callable_raising_exception, num_iterations, call_error_string, real_work);
});
m.def("pr1895_original_foo", pr1895_original_foo);
}
117 changes: 117 additions & 0 deletions test_perf_error_already_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import time

import pytest

from pybind11_tests import perf_error_already_set as m


def raise_runtime_error_from_python():
raise RuntimeError("Raised from Python.")


def recurse_first_then_call(
depth, callable, call_repetitions, call_error_string, real_work
):
if depth:
recurse_first_then_call(
depth - 1, callable, call_repetitions, call_error_string, real_work
)
else:
if call_error_string is None:
callable(call_repetitions)
else:
callable(
raise_runtime_error_from_python,
call_repetitions,
call_error_string,
real_work,
)


def find_call_repetitions(
recursion_depth,
callable,
call_error_string,
real_work,
time_delta_floor=1.0e-6,
target_elapsed_secs_multiplier=1.05, # Empirical.
target_elapsed_secs_tolerance=0.05,
max_iterations=100,
call_repetitions_first_pass=100,
call_repetitions_target_elapsed_secs=0.1, # 1.0 for real benchmarking.
):
td_target = call_repetitions_target_elapsed_secs * target_elapsed_secs_multiplier
crd = call_repetitions_first_pass
for _ in range(max_iterations):
t0 = time.time()
recurse_first_then_call(
recursion_depth, callable, crd, call_error_string, real_work
)
td = time.time() - t0
crd = max(1, int(td_target * crd / max(td, time_delta_floor)))
if abs(td - td_target) / td_target < target_elapsed_secs_tolerance:
return crd
raise RuntimeError("find_call_repetitions failure: max_iterations exceeded.")


def pr1895_original_foo(num_iterations):
assert num_iterations >= 0
while num_iterations:
m.pr1895_original_foo()
num_iterations -= 1


@pytest.mark.parametrize(
"perf_name",
[
"pure_unwind",
"err_set_unwind_err_clear",
"err_set_err_clear",
"err_set_error_already_set",
"err_set_err_fetch",
"error_already_set_restore",
"err_fetch_err_restore",
"pr1895_original_foo",
],
)
def test_perf(perf_name):
print(flush=True)
if perf_name == "pr1895_original_foo":
callable = pr1895_original_foo
else:
callable = getattr(m, perf_name)
if perf_name in ("pure_unwind", "pr1895_original_foo"):
real_work_list = [None]
call_error_string_list = [None]
else:
real_work_list = [0, 10000]
call_error_string_list = [False, True]
for real_work in real_work_list:
for recursion_depth in [0, 100]:
first_per_call = None
for call_error_string in call_error_string_list:
call_repetitions = find_call_repetitions(
recursion_depth, callable, call_error_string, real_work
)
t0 = time.time()
recurse_first_then_call(
recursion_depth,
callable,
call_repetitions,
call_error_string,
real_work,
)
secs = time.time() - t0
u_secs = secs * 10e6
per_call = u_secs / call_repetitions
if first_per_call is None:
relative_to_first = ""
first_per_call = per_call
else:
rel_percent = first_per_call / per_call * 100
relative_to_first = f",{rel_percent:.2f}%"
print(
f"PERF {perf_name},{recursion_depth},{call_repetitions},{call_error_string},"
f"{real_work},{secs:.3f}s,{per_call:.6f}μs{relative_to_first}",
flush=True,
)

0 comments on commit 7765113

Please sign in to comment.