mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05:11 +00:00
avoid C++ -> Python -> C++ overheads when passing around function objects
This commit is contained in:
parent
52269e91aa
commit
954b7932fe
@ -185,21 +185,32 @@ The following interactive session shows how to call them from Python.
|
||||
>>> plus_1(number=43)
|
||||
44L
|
||||
|
||||
.. note::
|
||||
|
||||
This functionality is very useful when generating bindings for callbacks in
|
||||
C++ libraries (e.g. a graphical user interface library).
|
||||
|
||||
The file :file:`example/example5.cpp` contains a complete example that
|
||||
demonstrates how to work with callbacks and anonymous functions in more detail.
|
||||
|
||||
.. warning::
|
||||
|
||||
Keep in mind that passing a function from C++ to Python (or vice versa)
|
||||
will instantiate a piece of wrapper code that translates function
|
||||
invocations between the two languages. Copying the same function back and
|
||||
forth between Python and C++ many times in a row will cause these wrappers
|
||||
to accumulate, which can decrease performance.
|
||||
invocations between the two languages. Naturally, this translation
|
||||
increases the computational cost of each function call somewhat. A
|
||||
problematic situation can arise when a function is copied back and forth
|
||||
between Python and C++ many times in a row, in which case the underlying
|
||||
wrappers will accumulate correspondingly. The resulting long sequence of
|
||||
C++ -> Python -> C++ -> ... roundtrips can significantly decrease
|
||||
performance.
|
||||
|
||||
There is one exception: pybind11 detects case where a stateless function
|
||||
(i.e. a function pointer or a lambda function without captured variables)
|
||||
is passed as an argument to another C++ function exposed in Python. In this
|
||||
case, there is no overhead. Pybind11 will extract the underlying C++
|
||||
function pointer from the wrapped function to sidestep a potential C++ ->
|
||||
Python -> C++ roundtrip. This is demonstrated in Example 5.
|
||||
|
||||
.. note::
|
||||
|
||||
This functionality is very useful when generating bindings for callbacks in
|
||||
C++ libraries (e.g. GUI libraries, asynchronous networking libraries, etc.).
|
||||
|
||||
The file :file:`example/example5.cpp` contains a complete example that
|
||||
demonstrates how to work with callbacks and anonymous functions in more detail.
|
||||
|
||||
Overriding virtual functions in Python
|
||||
======================================
|
||||
|
@ -65,6 +65,29 @@ py::cpp_function test_callback5() {
|
||||
py::arg("number"));
|
||||
}
|
||||
|
||||
int dummy_function(int i) { return i + 1; }
|
||||
int dummy_function2(int i, int j) { return i + j; }
|
||||
std::function<int(int)> roundtrip(std::function<int(int)> f) {
|
||||
std::cout << "roundtrip.." << std::endl;
|
||||
return f;
|
||||
}
|
||||
|
||||
void test_dummy_function(const std::function<int(int)> &f) {
|
||||
using fn_type = int (*)(int);
|
||||
auto result = f.target<fn_type>();
|
||||
if (!result) {
|
||||
std::cout << "could not convert to a function pointer." << std::endl;
|
||||
auto r = f(1);
|
||||
std::cout << "eval(1) = " << r << std::endl;
|
||||
} else if (*result == dummy_function) {
|
||||
std::cout << "argument matches dummy_function" << std::endl;
|
||||
auto r = (*result)(1);
|
||||
std::cout << "eval(1) = " << r << std::endl;
|
||||
} else {
|
||||
std::cout << "argument does NOT match dummy_function. This should never happen!" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void init_ex5(py::module &m) {
|
||||
py::class_<Pet> pet_class(m, "Pet");
|
||||
pet_class
|
||||
@ -113,4 +136,10 @@ void init_ex5(py::module &m) {
|
||||
/* p should be cleaned up when the returned function is garbage collected */
|
||||
};
|
||||
});
|
||||
|
||||
/* Test if passing a function pointer from C++ -> Python -> C++ yields the original pointer */
|
||||
m.def("dummy_function", &dummy_function);
|
||||
m.def("dummy_function2", &dummy_function2);
|
||||
m.def("roundtrip", &roundtrip);
|
||||
m.def("test_dummy_function", &test_dummy_function);
|
||||
}
|
||||
|
@ -54,3 +54,30 @@ f = test_callback5()
|
||||
print("func(number=43) = %i" % f(number=43))
|
||||
|
||||
test_cleanup()
|
||||
|
||||
from example import dummy_function
|
||||
from example import dummy_function2
|
||||
from example import test_dummy_function
|
||||
from example import roundtrip
|
||||
|
||||
test_dummy_function(dummy_function)
|
||||
test_dummy_function(roundtrip(dummy_function))
|
||||
test_dummy_function(lambda x: x + 2)
|
||||
|
||||
try:
|
||||
test_dummy_function(dummy_function2)
|
||||
print("Problem!")
|
||||
except Exception as e:
|
||||
if 'Incompatible function arguments' in str(e):
|
||||
print("All OK!")
|
||||
else:
|
||||
print("Problem!")
|
||||
|
||||
try:
|
||||
test_dummy_function(lambda x, y: x + y)
|
||||
print("Problem!")
|
||||
except Exception as e:
|
||||
if 'missing 1 required positional argument' in str(e):
|
||||
print("All OK!")
|
||||
else:
|
||||
print("Problem!")
|
||||
|
@ -1,20 +1,13 @@
|
||||
Rabbit is a parrot
|
||||
Rabbit is a parrot
|
||||
Polly is a parrot
|
||||
Polly is a parrot
|
||||
Molly is a dog
|
||||
Molly is a dog
|
||||
Woof!
|
||||
func(43) = 44
|
||||
Payload constructor
|
||||
Payload copy constructor
|
||||
Payload move constructor
|
||||
Payload destructor
|
||||
Payload destructor
|
||||
Payload destructor
|
||||
Rabbit is a parrot
|
||||
Polly is a parrot
|
||||
Molly is a dog
|
||||
The following error is expected: Incompatible function arguments. The following argument types are supported:
|
||||
1. (example.Dog) -> NoneType
|
||||
Invoked with: <Pet object at 0>
|
||||
Invoked with: <example.Pet object at 0>
|
||||
Callback function 1 called!
|
||||
False
|
||||
Callback function 2 called : Hello, x, True, 5
|
||||
@ -24,4 +17,22 @@ False
|
||||
Callback function 3 called : Partial object with one argument
|
||||
False
|
||||
func(43) = 44
|
||||
func(43) = 44
|
||||
func(number=43) = 44
|
||||
Payload constructor
|
||||
Payload copy constructor
|
||||
Payload move constructor
|
||||
Payload destructor
|
||||
Payload destructor
|
||||
Payload destructor
|
||||
argument matches dummy_function
|
||||
eval(1) = 2
|
||||
roundtrip..
|
||||
argument matches dummy_function
|
||||
eval(1) = 2
|
||||
could not convert to a function pointer.
|
||||
eval(1) = 3
|
||||
could not convert to a function pointer.
|
||||
All OK!
|
||||
could not convert to a function pointer.
|
||||
All OK!
|
||||
|
@ -113,6 +113,9 @@ struct function_record {
|
||||
/// True if name == '__init__'
|
||||
bool is_constructor : 1;
|
||||
|
||||
/// True if this is a stateless function pointer
|
||||
bool is_stateless : 1;
|
||||
|
||||
/// True if the function has a '*args' argument
|
||||
bool has_args : 1;
|
||||
|
||||
|
@ -23,6 +23,29 @@ public:
|
||||
src_ = detail::get_function(src_);
|
||||
if (!src_ || !PyCallable_Check(src_.ptr()))
|
||||
return false;
|
||||
|
||||
{
|
||||
/*
|
||||
When passing a C++ function as an argument to another C++
|
||||
function via Python, every function call would normally involve
|
||||
a full C++ -> Python -> C++ roundtrip, which can be prohibitive.
|
||||
Here, we try to at least detect the case where the function is
|
||||
stateless (i.e. function pointer or lambda function without
|
||||
captured variables), in which case the roundtrip can be avoided.
|
||||
*/
|
||||
if (PyCFunction_Check(src_.ptr())) {
|
||||
capsule c(PyCFunction_GetSelf(src_.ptr()), true);
|
||||
auto rec = (function_record *) c;
|
||||
using FunctionType = Return (*) (Args...);
|
||||
|
||||
if (rec && rec->is_stateless && rec->data[1] == &typeid(FunctionType)) {
|
||||
struct capture { FunctionType f; };
|
||||
value = ((capture *) &rec->data)->f;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object src(src_, true);
|
||||
value = [src](Args... args) -> Return {
|
||||
gil_scoped_acquire acq;
|
||||
@ -35,7 +58,11 @@ public:
|
||||
|
||||
template <typename Func>
|
||||
static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) {
|
||||
return cpp_function(std::forward<Func>(f_), policy).release();
|
||||
auto result = f_.template target<Return (*)(Args...)>();
|
||||
if (result)
|
||||
return cpp_function(*result, policy).release();
|
||||
else
|
||||
return cpp_function(std::forward<Func>(f_), policy).release();
|
||||
}
|
||||
|
||||
PYBIND11_TYPE_CASTER(type, _("function<") +
|
||||
|
@ -82,6 +82,9 @@ protected:
|
||||
|
||||
/* Store the capture object directly in the function record if there is enough space */
|
||||
if (sizeof(capture) <= sizeof(rec->data)) {
|
||||
/* Without these pragmas, GCC warns that there might not be
|
||||
enough space to use the placement new operator. However, the
|
||||
'if' statement above ensures that this is the case. */
|
||||
#if defined(__GNUG__) && !defined(__clang__) && __GNUC__ >= 6
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wplacement-new"
|
||||
@ -118,7 +121,7 @@ protected:
|
||||
capture *cap = (capture *) (sizeof(capture) <= sizeof(rec->data)
|
||||
? &rec->data : rec->data[0]);
|
||||
|
||||
/* Perform the functioncall */
|
||||
/* Perform the function call */
|
||||
handle result = cast_out::cast(args_converter.template call<Return>(cap->f),
|
||||
rec->policy, parent);
|
||||
|
||||
@ -140,6 +143,16 @@ protected:
|
||||
|
||||
if (cast_in::has_args) rec->has_args = true;
|
||||
if (cast_in::has_kwargs) rec->has_kwargs = true;
|
||||
|
||||
/* Stash some additional information used by an important optimization in 'functional.h' */
|
||||
using FunctionType = Return (*)(Args...);
|
||||
constexpr bool is_function_ptr =
|
||||
std::is_convertible<Func, FunctionType>::value &&
|
||||
sizeof(capture) == sizeof(void *);
|
||||
if (is_function_ptr) {
|
||||
rec->is_stateless = true;
|
||||
rec->data[1] = (void *) &typeid(FunctionType);
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a function call with Python (generic non-templated code goes here)
|
||||
@ -157,6 +170,7 @@ protected:
|
||||
else if (a.value)
|
||||
a.descr = strdup(((std::string) ((object) handle(a.value).attr("__repr__"))().str()).c_str());
|
||||
}
|
||||
|
||||
auto const ®istered_types = detail::get_internals().registered_types_cpp;
|
||||
|
||||
/* Generate a proper function signature */
|
||||
@ -215,10 +229,10 @@ protected:
|
||||
rec->name = strdup("__nonzero__");
|
||||
}
|
||||
#endif
|
||||
|
||||
rec->signature = strdup(signature.c_str());
|
||||
rec->args.shrink_to_fit();
|
||||
rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");
|
||||
rec->is_stateless = false;
|
||||
rec->has_args = false;
|
||||
rec->has_kwargs = false;
|
||||
rec->nargs = (uint16_t) args;
|
||||
|
Loading…
Reference in New Issue
Block a user