mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 22:52:01 +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)
|
>>> plus_1(number=43)
|
||||||
44L
|
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::
|
.. warning::
|
||||||
|
|
||||||
Keep in mind that passing a function from C++ to Python (or vice versa)
|
Keep in mind that passing a function from C++ to Python (or vice versa)
|
||||||
will instantiate a piece of wrapper code that translates function
|
will instantiate a piece of wrapper code that translates function
|
||||||
invocations between the two languages. Copying the same function back and
|
invocations between the two languages. Naturally, this translation
|
||||||
forth between Python and C++ many times in a row will cause these wrappers
|
increases the computational cost of each function call somewhat. A
|
||||||
to accumulate, which can decrease performance.
|
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
|
Overriding virtual functions in Python
|
||||||
======================================
|
======================================
|
||||||
|
@ -65,6 +65,29 @@ py::cpp_function test_callback5() {
|
|||||||
py::arg("number"));
|
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) {
|
void init_ex5(py::module &m) {
|
||||||
py::class_<Pet> pet_class(m, "Pet");
|
py::class_<Pet> pet_class(m, "Pet");
|
||||||
pet_class
|
pet_class
|
||||||
@ -113,4 +136,10 @@ void init_ex5(py::module &m) {
|
|||||||
/* p should be cleaned up when the returned function is garbage collected */
|
/* 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))
|
print("func(number=43) = %i" % f(number=43))
|
||||||
|
|
||||||
test_cleanup()
|
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
|
||||||
|
Rabbit is a parrot
|
||||||
Polly is a parrot
|
Polly is a parrot
|
||||||
|
Polly is a parrot
|
||||||
|
Molly is a dog
|
||||||
Molly is a dog
|
Molly is a dog
|
||||||
Woof!
|
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:
|
The following error is expected: Incompatible function arguments. The following argument types are supported:
|
||||||
1. (example.Dog) -> NoneType
|
1. (example.Dog) -> NoneType
|
||||||
Invoked with: <Pet object at 0>
|
Invoked with: <example.Pet object at 0>
|
||||||
Callback function 1 called!
|
Callback function 1 called!
|
||||||
False
|
False
|
||||||
Callback function 2 called : Hello, x, True, 5
|
Callback function 2 called : Hello, x, True, 5
|
||||||
@ -24,4 +17,22 @@ False
|
|||||||
Callback function 3 called : Partial object with one argument
|
Callback function 3 called : Partial object with one argument
|
||||||
False
|
False
|
||||||
func(43) = 44
|
func(43) = 44
|
||||||
|
func(43) = 44
|
||||||
func(number=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__'
|
/// True if name == '__init__'
|
||||||
bool is_constructor : 1;
|
bool is_constructor : 1;
|
||||||
|
|
||||||
|
/// True if this is a stateless function pointer
|
||||||
|
bool is_stateless : 1;
|
||||||
|
|
||||||
/// True if the function has a '*args' argument
|
/// True if the function has a '*args' argument
|
||||||
bool has_args : 1;
|
bool has_args : 1;
|
||||||
|
|
||||||
|
@ -23,6 +23,29 @@ public:
|
|||||||
src_ = detail::get_function(src_);
|
src_ = detail::get_function(src_);
|
||||||
if (!src_ || !PyCallable_Check(src_.ptr()))
|
if (!src_ || !PyCallable_Check(src_.ptr()))
|
||||||
return false;
|
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);
|
object src(src_, true);
|
||||||
value = [src](Args... args) -> Return {
|
value = [src](Args... args) -> Return {
|
||||||
gil_scoped_acquire acq;
|
gil_scoped_acquire acq;
|
||||||
@ -35,6 +58,10 @@ public:
|
|||||||
|
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) {
|
static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) {
|
||||||
|
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();
|
return cpp_function(std::forward<Func>(f_), policy).release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +82,9 @@ protected:
|
|||||||
|
|
||||||
/* Store the capture object directly in the function record if there is enough space */
|
/* Store the capture object directly in the function record if there is enough space */
|
||||||
if (sizeof(capture) <= sizeof(rec->data)) {
|
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
|
#if defined(__GNUG__) && !defined(__clang__) && __GNUC__ >= 6
|
||||||
# pragma GCC diagnostic push
|
# pragma GCC diagnostic push
|
||||||
# pragma GCC diagnostic ignored "-Wplacement-new"
|
# pragma GCC diagnostic ignored "-Wplacement-new"
|
||||||
@ -140,6 +143,16 @@ protected:
|
|||||||
|
|
||||||
if (cast_in::has_args) rec->has_args = true;
|
if (cast_in::has_args) rec->has_args = true;
|
||||||
if (cast_in::has_kwargs) rec->has_kwargs = 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)
|
/// Register a function call with Python (generic non-templated code goes here)
|
||||||
@ -157,6 +170,7 @@ protected:
|
|||||||
else if (a.value)
|
else if (a.value)
|
||||||
a.descr = strdup(((std::string) ((object) handle(a.value).attr("__repr__"))().str()).c_str());
|
a.descr = strdup(((std::string) ((object) handle(a.value).attr("__repr__"))().str()).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const ®istered_types = detail::get_internals().registered_types_cpp;
|
auto const ®istered_types = detail::get_internals().registered_types_cpp;
|
||||||
|
|
||||||
/* Generate a proper function signature */
|
/* Generate a proper function signature */
|
||||||
@ -215,10 +229,10 @@ protected:
|
|||||||
rec->name = strdup("__nonzero__");
|
rec->name = strdup("__nonzero__");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
rec->signature = strdup(signature.c_str());
|
rec->signature = strdup(signature.c_str());
|
||||||
rec->args.shrink_to_fit();
|
rec->args.shrink_to_fit();
|
||||||
rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");
|
rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");
|
||||||
|
rec->is_stateless = false;
|
||||||
rec->has_args = false;
|
rec->has_args = false;
|
||||||
rec->has_kwargs = false;
|
rec->has_kwargs = false;
|
||||||
rec->nargs = (uint16_t) args;
|
rec->nargs = (uint16_t) args;
|
||||||
|
Loading…
Reference in New Issue
Block a user