Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pybind11
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open
pybind11
Commits
954b7932
Commit
954b7932
authored
Jul 10, 2016
by
Wenzel Jakob
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
avoid C++ -> Python -> C++ overheads when passing around function objects
parent
52269e91
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
145 additions
and
23 deletions
+145
-23
docs/advanced.rst
+20
-9
example/example5.cpp
+29
-0
example/example5.py
+27
-0
example/example5.ref
+22
-11
include/pybind11/attr.h
+3
-0
include/pybind11/functional.h
+28
-1
include/pybind11/pybind11.h
+16
-2
No files found.
docs/advanced.rst
View file @
954b7932
...
...
@@ -185,22 +185,33 @@ The following interactive session shows how to call them from Python.
>>> plus_1(number=43)
44L
.. 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. 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.
a graphical user interface library
).
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.
.. 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.
Overriding virtual functions in Python
======================================
...
...
example/example5.cpp
View file @
954b7932
...
...
@@ -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
);
}
example/example5.py
View file @
954b7932
...
...
@@ -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!"
)
example/example5.ref
View file @
954b7932
Rabbit is a parrot
Polly is a parrot
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
Polly is a parrot
Molly is a dog
Molly is a dog
Woof!
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!
include/pybind11/attr.h
View file @
954b7932
...
...
@@ -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
;
...
...
include/pybind11/functional.h
View file @
954b7932
...
...
@@ -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<"
)
+
...
...
include/pybind11/pybind11.h
View file @
954b7932
...
...
@@ -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
&
registered_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
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment