Skip to content

Commit 73b6804

Browse files
committed
Split pybind11 into headers and cpp files to speedup compilation
As discussed at #2322, the split is opt-in, and has been observed to speed up the compilation of the gem5 project by around 40% from 25 minutes to 15 minutes. If the PYBIND11_DECLARATIONS_ONLY is not defined, then the .cpp files are included in the .h files, and everything works header-only as was the case before this commit. If PYBIND11_DECLARATIONS_ONLY=1, then only declarations are made visible from, the header files, and the user must the user must compile the pybind11 .cpp files, also using PYBIND11_DECLARATIONS_ONLY=1, into objects and add link those into the final link. This commit also updates CMakeLists to automate building those object files. This commit has been tested as follows. First, the original build and pytest (without PYBIND11_DECLARATIONS_ONLY) work as before: mkdir build cmake .. make -j `nproc` make pytest TODO: if this commit gets traction, I will try to add a test for the PYBIND11_DECLARATIONS_ONLY version too, otherwise it will likely break. I'd just re-run the entire pybind with PYBIND11_DECLARATIONS_ONLY as well every time. The commit has also been tested with the following minimal manual example: class_test.cpp ``` struct ClassTest { ClassTest(const std::string &name) : name(name) {} void setName(const std::string &name_) { name = name_; } const std::string &getName() const { return name; } std::string name; }; struct ClassTestDerived : ClassTest { ClassTestDerived(const std::string &name, const std::string &name2) : ClassTest(name), name2(name2) {} std::string getName2() { return name + name2 + "2"; } std::string name2; }; namespace py = pybind11; PYBIND11_MODULE(class_test, m) { m.doc() = "pybind11 example plugin"; py::class_<ClassTest>(m, "ClassTest") .def(py::init<const std::string &>()) .def("setName", &ClassTest::setName) .def("getName", &ClassTest::getName) .def_readwrite("name", &ClassTest::name); py::class_<ClassTestDerived, ClassTest>(m, "ClassTestDerived") .def(py::init<const std::string &, const std::string &>()) .def("getName2", &ClassTestDerived::getName2) .def_readwrite("name", &ClassTestDerived::name); } ``` class_test_main.py ``` import class_test my_class_test = class_test.ClassTest('abc'); assert(my_class_test.getName() == 'abc') my_class_test.setName('012') assert(my_class_test.name == '012') my_class_test_derived = class_test.ClassTestDerived('abc', 'def'); assert(my_class_test_derived.getName2() == 'abcdef2') ``` without PYBIND11_DECLARATIONS_ONLY (`python3-config --cflags`) is opened up and hacked to point to the custom pybind11 source: ``` g++ \ -save-temps \ -I/usr/include/python3.8 \ -I/home/ciro/git/pybind11/include \ -Wno-unused-result \ -Wsign-compare \ -g \ -fdebug-prefix-map=/build/python3.8-fKk4GY/python3.8-3.8.2=. \ -specs=/usr/share/dpkg/no-pie-compile.specs \ -fstack-protector \ -Wformat \ -Werror=format-security \ -DNDEBUG \ -g \ -fwrapv \ -O3 \ -Wall \ -shared \ -std=c++11 \ -fPIC class_test.cpp \ -o class_test`python3-config --extension-suffix` \ `python3-config --libs` \ ; ./class_test_main.py ``` with PYBIND11_DECLARATIONS_ONLY: ``` g++ \ -DPYBIND11_DECLARATIONS_ONLY=1 \ -save-temps \ -I/usr/include/python3.8 \ -I/home/ciro/git/pybind11/include \ -Wno-unused-result \ -Wsign-compare \ -g \ -fdebug-prefix-map=/build/python3.8-fKk4GY/python3.8-3.8.2=. \ -specs=/usr/share/dpkg/no-pie-compile.specs \ -fstack-protector \ -Wformat \ -Werror=format-security \ -DNDEBUG \ -g \ -fwrapv \ -O3 \ -Wall \ -shared \ -std=c++11 \ -fPIC class_test.cpp \ /home/ciro/git/pybind11/build/libpybind11.a \ -o class_test`python3-config --extension-suffix` \ `python3-config --libs` \ ; ./class_test_main.py ```
1 parent 6a19278 commit 73b6804

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+4452
-3458
lines changed

.github/workflows/ci.yml

-30
Original file line numberDiff line numberDiff line change
@@ -327,33 +327,3 @@ jobs:
327327
- name: Run tests
328328
run: make pytest -j 2
329329
working-directory: /build-tests
330-
331-
332-
doxygen:
333-
name: "Documentation build test"
334-
runs-on: ubuntu-latest
335-
container: alpine:3.12
336-
337-
steps:
338-
- uses: actions/checkout@v2
339-
340-
- name: Install system requirements
341-
run: apk add doxygen python3-dev
342-
343-
- name: Ensure pip
344-
run: python3 -m ensurepip
345-
346-
- name: Install docs & setup requirements
347-
run: python3 -m pip install -r docs/requirements.txt pytest setuptools
348-
349-
- name: Build docs
350-
run: python3 -m sphinx -W -b html docs docs/.build
351-
352-
- name: Make SDist
353-
run: python3 setup.py sdist
354-
355-
- name: Compare Dists (headers only)
356-
run: |
357-
python3 -m pip install --user -U ./dist/*
358-
installed=$(python3 -c "import pybind11; print(pybind11.get_include(True) + '/pybind11')")
359-
diff -rq $installed ./include/pybind11

CMakeLists.txt

+18
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@ add_library(pybind11::headers ALIAS pybind11_headers) # easier to use/remember
161161

162162
include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake")
163163

164+
option(PYBIND11_PRECOMPILE "Precompile parts of pybind11" ON)
165+
166+
if(PYBIND11_PRECOMPILE)
167+
file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
168+
if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy")
169+
# Pypy does not support embedding.
170+
list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/embed.cpp)
171+
endif()
172+
# Produce libpybind11.a, which contains position independent object files.
173+
# that may be used to create a shared library.
174+
add_library(pybind11_lib STATIC ${SOURCES} ${PYBIND11_HEADERS})
175+
set_property(TARGET pybind11_lib PROPERTY POSITION_INDEPENDENT_CODE ON)
176+
target_compile_definitions(pybind11_lib PUBLIC -DPYBIND11_DECLARATIONS_ONLY=1)
177+
add_library(pybind11::lib ALIAS pybind11_lib)
178+
target_link_libraries(pybind11_lib PRIVATE pybind11::pybind11)
179+
target_link_libraries(pybind11_lib PUBLIC pybind11::headers)
180+
endif()
181+
164182
if(NOT PYBIND11_MASTER_PROJECT AND NOT pybind11_FIND_QUIETLY)
165183
message(STATUS "Using pybind11: (version \"${pybind11_VERSION}\" ${pybind11_VERSION_TYPE})")
166184
endif()

docs/Doxyfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ XML_OUTPUT = .build/doxygenxml
99
XML_PROGRAMLISTING = YES
1010

1111
MACRO_EXPANSION = YES
12-
EXPAND_ONLY_PREDEF = YES
12+
EXPAND_ONLY_PREDEF = NO
1313
EXPAND_AS_DEFINED = PYBIND11_RUNTIME_EXCEPTION
1414

1515
ALIASES = "rst=\verbatim embed:rst"

include/pybind11/attr-inl.h

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include "attr.h"
2+
3+
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
4+
5+
PYBIND11_NAMESPACE_BEGIN(detail)
6+
7+
PYBIND11_INLINE void type_record::add_base(const std::type_info &base, void *(*caster)(void *)) {
8+
auto base_info = detail::get_type_info(base, false);
9+
if (!base_info) {
10+
std::string tname(base.name());
11+
detail::clean_type_id(tname);
12+
pybind11_fail("generic_type: type \"" + std::string(name) +
13+
"\" referenced unknown base type \"" + tname + "\"");
14+
}
15+
16+
if (default_holder != base_info->default_holder) {
17+
std::string tname(base.name());
18+
detail::clean_type_id(tname);
19+
pybind11_fail("generic_type: type \"" + std::string(name) + "\" " +
20+
(default_holder ? "does not have" : "has") +
21+
" a non-default holder type while its base \"" + tname + "\" " +
22+
(base_info->default_holder ? "does not" : "does"));
23+
}
24+
25+
bases.append((PyObject *) base_info->type);
26+
27+
if (base_info->type->tp_dictoffset != 0)
28+
dynamic_attr = true;
29+
30+
if (caster)
31+
base_info->implicit_casts.emplace_back(type, caster);
32+
}
33+
34+
PYBIND11_INLINE function_call::function_call(const function_record &f, handle p) :
35+
func(f), parent(p) {
36+
args.reserve(f.nargs);
37+
args_convert.reserve(f.nargs);
38+
}
39+
40+
PYBIND11_INLINE void process_kwonly_arg(const arg &a, function_record *r) {
41+
if (!a.name || strlen(a.name) == 0)
42+
pybind11_fail("arg(): cannot specify an unnamed argument after an kwonly() annotation");
43+
++r->nargs_kwonly;
44+
}
45+
46+
PYBIND11_NAMESPACE_END(detail)
47+
48+
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/attr.h

+7-38
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ enum op_id : int;
119119
enum op_type : int;
120120
struct undefined_t;
121121
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
122-
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
122+
void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
123123

124124
/// Internal data structure which holds metadata about a keyword argument
125125
struct argument_record {
@@ -267,40 +267,9 @@ struct type_record {
267267
/// Is the class inheritable from python classes?
268268
bool is_final : 1;
269269

270-
PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *)) {
271-
auto base_info = detail::get_type_info(base, false);
272-
if (!base_info) {
273-
std::string tname(base.name());
274-
detail::clean_type_id(tname);
275-
pybind11_fail("generic_type: type \"" + std::string(name) +
276-
"\" referenced unknown base type \"" + tname + "\"");
277-
}
278-
279-
if (default_holder != base_info->default_holder) {
280-
std::string tname(base.name());
281-
detail::clean_type_id(tname);
282-
pybind11_fail("generic_type: type \"" + std::string(name) + "\" " +
283-
(default_holder ? "does not have" : "has") +
284-
" a non-default holder type while its base \"" + tname + "\" " +
285-
(base_info->default_holder ? "does not" : "does"));
286-
}
287-
288-
bases.append((PyObject *) base_info->type);
289-
290-
if (base_info->type->tp_dictoffset != 0)
291-
dynamic_attr = true;
292-
293-
if (caster)
294-
base_info->implicit_casts.emplace_back(type, caster);
295-
}
270+
PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *));
296271
};
297272

298-
inline function_call::function_call(const function_record &f, handle p) :
299-
func(f), parent(p) {
300-
args.reserve(f.nargs);
301-
args_convert.reserve(f.nargs);
302-
}
303-
304273
/// Tag for a new-style `__init__` defined in `detail/init.h`
305274
struct is_new_style_constructor { };
306275

@@ -366,11 +335,7 @@ template <> struct process_attribute<is_new_style_constructor> : process_attribu
366335
static void init(const is_new_style_constructor &, function_record *r) { r->is_new_style_constructor = true; }
367336
};
368337

369-
inline void process_kwonly_arg(const arg &a, function_record *r) {
370-
if (!a.name || strlen(a.name) == 0)
371-
pybind11_fail("arg(): cannot specify an unnamed argument after an kwonly() annotation");
372-
++r->nargs_kwonly;
373-
}
338+
void process_kwonly_arg(const arg &a, function_record *r);
374339

375340
/// Process a keyword argument attribute (*without* a default value)
376341
template <> struct process_attribute<arg> : process_attribute_default<arg> {
@@ -526,3 +491,7 @@ constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) {
526491

527492
PYBIND11_NAMESPACE_END(detail)
528493
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
494+
495+
#if !defined(PYBIND11_DECLARATIONS_ONLY)
496+
#include "attr-inl.h"
497+
#endif

include/pybind11/buffer_info-inl.h

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include "buffer_info.h"
2+
3+
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
4+
5+
PYBIND11_INLINE buffer_info::buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim,
6+
detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in, bool readonly)
7+
: ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim),
8+
shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) {
9+
if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size())
10+
pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length");
11+
for (size_t i = 0; i < (size_t) ndim; ++i)
12+
size *= shape[i];
13+
}
14+
15+
PYBIND11_INLINE buffer_info::buffer_info(Py_buffer *view, bool ownview)
16+
: buffer_info(view->buf, view->itemsize, view->format, view->ndim,
17+
{view->shape, view->shape + view->ndim}, {view->strides, view->strides + view->ndim}, view->readonly) {
18+
this->m_view = view;
19+
this->ownview = ownview;
20+
}
21+
22+
PYBIND11_INLINE buffer_info::buffer_info(buffer_info &&other) {
23+
(*this) = std::move(other);
24+
}
25+
26+
PYBIND11_INLINE buffer_info& buffer_info::operator=(buffer_info &&rhs) {
27+
ptr = rhs.ptr;
28+
itemsize = rhs.itemsize;
29+
size = rhs.size;
30+
format = std::move(rhs.format);
31+
ndim = rhs.ndim;
32+
shape = std::move(rhs.shape);
33+
strides = std::move(rhs.strides);
34+
std::swap(m_view, rhs.m_view);
35+
std::swap(ownview, rhs.ownview);
36+
readonly = rhs.readonly;
37+
return *this;
38+
}
39+
40+
PYBIND11_INLINE buffer_info::~buffer_info() {
41+
if (m_view && ownview) { PyBuffer_Release(m_view); delete m_view; }
42+
}
43+
44+
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/buffer_info.h

+9-33
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,7 @@ struct buffer_info {
2727
buffer_info() { }
2828

2929
buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim,
30-
detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in, bool readonly=false)
31-
: ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim),
32-
shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) {
33-
if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size())
34-
pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length");
35-
for (size_t i = 0; i < (size_t) ndim; ++i)
36-
size *= shape[i];
37-
}
30+
detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in, bool readonly=false);
3831

3932
template <typename T>
4033
buffer_info(T *ptr, detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in, bool readonly=false)
@@ -51,37 +44,16 @@ struct buffer_info {
5144
buffer_info(const T *ptr, ssize_t size, bool readonly=true)
5245
: buffer_info(const_cast<T*>(ptr), sizeof(T), format_descriptor<T>::format(), size, readonly) { }
5346

54-
explicit buffer_info(Py_buffer *view, bool ownview = true)
55-
: buffer_info(view->buf, view->itemsize, view->format, view->ndim,
56-
{view->shape, view->shape + view->ndim}, {view->strides, view->strides + view->ndim}, view->readonly) {
57-
this->m_view = view;
58-
this->ownview = ownview;
59-
}
47+
explicit buffer_info(Py_buffer *view, bool ownview = true);
6048

6149
buffer_info(const buffer_info &) = delete;
6250
buffer_info& operator=(const buffer_info &) = delete;
6351

64-
buffer_info(buffer_info &&other) {
65-
(*this) = std::move(other);
66-
}
52+
buffer_info(buffer_info &&other);
6753

68-
buffer_info& operator=(buffer_info &&rhs) {
69-
ptr = rhs.ptr;
70-
itemsize = rhs.itemsize;
71-
size = rhs.size;
72-
format = std::move(rhs.format);
73-
ndim = rhs.ndim;
74-
shape = std::move(rhs.shape);
75-
strides = std::move(rhs.strides);
76-
std::swap(m_view, rhs.m_view);
77-
std::swap(ownview, rhs.ownview);
78-
readonly = rhs.readonly;
79-
return *this;
80-
}
54+
buffer_info& operator=(buffer_info &&rhs);
8155

82-
~buffer_info() {
83-
if (m_view && ownview) { PyBuffer_Release(m_view); delete m_view; }
84-
}
56+
~buffer_info();
8557

8658
Py_buffer *view() const { return m_view; }
8759
Py_buffer *&view() { return m_view; }
@@ -114,3 +86,7 @@ template <typename T> struct compare_buffer_info<T, detail::enable_if_t<std::is_
11486

11587
PYBIND11_NAMESPACE_END(detail)
11688
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
89+
90+
#if !defined(PYBIND11_DECLARATIONS_ONLY)
91+
#include "buffer_info-inl.h"
92+
#endif

0 commit comments

Comments
 (0)