Skip to content

Commit a8865ee

Browse files
committed
Use semi-constexpr signatures on MSVC
MSCV does not allow `&typeid(T)` in constexpr contexts, but the string part of the type signature can still be constexpr. In order to avoid `typeid` as long as possible, `descr` is modified to collect type information as template parameters instead of constexpr `typeid`. The actual `std::type_info` pointers are only collected in the end, as a `constexpr` (gcc/clang) or regular (MSVC) function call. Not only does it significantly reduce binary size on MSVC, gcc/clang benefit a little bit as well, since they can skip some intermediate `std::type_info*` arrays.
1 parent 6fca340 commit a8865ee

File tree

7 files changed

+67
-109
lines changed

7 files changed

+67
-109
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,8 @@ In addition to the core functionality, pybind11 provides some extra goodies:
8787
[reported](http://graylab.jhu.edu/RosettaCon2016/PyRosetta-4.pdf) a binary
8888
size reduction of **5.4x** and compile time reduction by **5.8x**.
8989

90-
- When supported by the compiler, two new C++14 features (relaxed constexpr and
91-
return value deduction) are used to precompute function signatures at compile
92-
time, leading to smaller binaries.
90+
- Function signatures are precomputed at compile time (using ``constexpr``),
91+
leading to smaller binaries.
9392

9493
- With little extra effort, C++ types can be pickled and unpickled similar to
9594
regular Python objects.

docs/changelog.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ Starting with version 1.8.0, pybind11 releases use a `semantic versioning
99
v2.3.0 (Not yet released)
1010
-----------------------------------------------------
1111

12-
* TBD
12+
* Significantly reduced module binary size (10-20%) when compiled in C++11 mode
13+
with GCC/Clang, or in any mode with MSVC. Function signatures are now always
14+
precomputed at compile time (this was previously only available in C++14 mode
15+
for non-MSVC compilers).
16+
`#934 <https://github.com/pybind/pybind11/pull/934>`_.
1317

1418
v2.2.0 (August 31, 2017)
1519
-----------------------------------------------------

docs/faq.rst

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -228,36 +228,6 @@ In addition to decreasing binary size, ``-fvisibility=hidden`` also avoids
228228
potential serious issues when loading multiple modules and is required for
229229
proper pybind operation. See the previous FAQ entry for more details.
230230

231-
Another aspect that can require a fair bit of code are function signature
232-
descriptions. pybind11 automatically generates human-readable function
233-
signatures for docstrings, e.g.:
234-
235-
.. code-block:: none
236-
237-
| __init__(...)
238-
| __init__(*args, **kwargs)
239-
| Overloaded function.
240-
|
241-
| 1. __init__(example.Example1) -> NoneType
242-
|
243-
| Docstring for overload #1 goes here
244-
|
245-
| 2. __init__(example.Example1, int) -> NoneType
246-
|
247-
| Docstring for overload #2 goes here
248-
|
249-
| 3. __init__(example.Example1, example.Example1) -> NoneType
250-
|
251-
| Docstring for overload #3 goes here
252-
253-
254-
In C++11 mode, these are generated at run time using string concatenation,
255-
which can amount to 10-20% of the size of the resulting binary. If you can,
256-
enable C++14 language features (using ``-std=c++14`` for GCC/Clang), in which
257-
case signatures are efficiently pre-generated at compile time. Unfortunately,
258-
Visual Studio's C++14 support (``constexpr``) is not good enough as of April
259-
2016, so it always uses the more expensive run-time approach.
260-
261231
Working with ancient Visual Studio 2009 builds on Windows
262232
=========================================================
263233

docs/intro.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,8 @@ In addition to the core functionality, pybind11 provides some extra goodies:
7777
of `PyRosetta`_, an enormous Boost.Python binding project, reported a binary
7878
size reduction of **5.4x** and compile time reduction by **5.8x**.
7979

80-
- When supported by the compiler, two new C++14 features (relaxed constexpr and
81-
return value deduction) are used to precompute function signatures at compile
82-
time, leading to smaller binaries.
80+
- Function signatures are precomputed at compile time (using ``constexpr``),
81+
leading to smaller binaries.
8382

8483
- With little extra effort, C++ types can be pickled and unpickled similar to
8584
regular Python objects.

include/pybind11/detail/descr.h

Lines changed: 52 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -14,99 +14,85 @@
1414
NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
1515
NAMESPACE_BEGIN(detail)
1616

17+
#if !defined(_MSC_VER)
18+
# define PYBIND11_DESCR_CONSTEXPR static constexpr
19+
#else
20+
# define PYBIND11_DESCR_CONSTEXPR const
21+
#endif
22+
1723
/* Concatenate type signatures at compile time */
18-
template <size_t Size1, size_t Size2> class descr {
19-
template <size_t Size1_, size_t Size2_> friend class descr;
20-
public:
21-
constexpr descr() = default;
22-
23-
constexpr descr(char const (&text) [Size1+1], const std::type_info * const (&types)[Size2+1])
24-
: descr(text, types,
25-
make_index_sequence<Size1>(),
26-
make_index_sequence<Size2>()) { }
27-
28-
constexpr const char *text() const { return m_text; }
29-
constexpr const std::type_info * const * types() const { return m_types; }
30-
31-
template <size_t OtherSize1, size_t OtherSize2>
32-
constexpr descr<Size1 + OtherSize1, Size2 + OtherSize2> operator+(const descr<OtherSize1, OtherSize2> &other) const {
33-
return concat(other,
34-
make_index_sequence<Size1>(),
35-
make_index_sequence<Size2>(),
36-
make_index_sequence<OtherSize1>(),
37-
make_index_sequence<OtherSize2>());
38-
}
24+
template <size_t N, typename... Ts>
25+
struct descr {
26+
char text[N + 1];
3927

40-
protected:
41-
template <size_t... Indices1, size_t... Indices2>
42-
constexpr descr(
43-
char const (&text) [Size1+1],
44-
const std::type_info * const (&types) [Size2+1],
45-
index_sequence<Indices1...>, index_sequence<Indices2...>)
46-
: m_text{text[Indices1]..., '\0'},
47-
m_types{types[Indices2]..., nullptr } {}
48-
49-
template <size_t OtherSize1, size_t OtherSize2, size_t... Indices1,
50-
size_t... Indices2, size_t... OtherIndices1, size_t... OtherIndices2>
51-
constexpr descr<Size1 + OtherSize1, Size2 + OtherSize2>
52-
concat(const descr<OtherSize1, OtherSize2> &other,
53-
index_sequence<Indices1...>, index_sequence<Indices2...>,
54-
index_sequence<OtherIndices1...>, index_sequence<OtherIndices2...>) const {
55-
return descr<Size1 + OtherSize1, Size2 + OtherSize2>(
56-
{ m_text[Indices1]..., other.m_text[OtherIndices1]..., '\0' },
57-
{ m_types[Indices2]..., other.m_types[OtherIndices2]..., nullptr }
58-
);
59-
}
28+
constexpr descr() : text{'\0'} { }
29+
constexpr descr(char const (&s)[N+1]) : descr(s, make_index_sequence<N>()) { }
6030

61-
protected:
62-
char m_text[Size1 + 1];
63-
const std::type_info * m_types[Size2 + 1];
31+
template <size_t... Is>
32+
constexpr descr(char const (&s)[N+1], index_sequence<Is...>) : text{s[Is]..., '\0'} { }
33+
34+
template <typename... Chars>
35+
constexpr descr(char c, Chars... cs) : text{c, static_cast<char>(cs)..., '\0'} { }
36+
37+
static constexpr std::array<const std::type_info *, sizeof...(Ts) + 1> types() {
38+
return {{&typeid(Ts)..., nullptr}};
39+
}
6440
};
6541

66-
template <size_t Size> constexpr descr<Size - 1, 0> _(char const(&text)[Size]) {
67-
return descr<Size - 1, 0>(text, { nullptr });
42+
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2, size_t... Is1, size_t... Is2>
43+
constexpr descr<N1 + N2, Ts1..., Ts2...> plus_impl(const descr<N1, Ts1...> &a, const descr<N2, Ts2...> &b,
44+
index_sequence<Is1...>, index_sequence<Is2...>) {
45+
return {a.text[Is1]..., b.text[Is2]...};
6846
}
6947

48+
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
49+
constexpr descr<N1 + N2, Ts1..., Ts2...> operator+(const descr<N1, Ts1...> &a, const descr<N2, Ts2...> &b) {
50+
return plus_impl(a, b, make_index_sequence<N1>(), make_index_sequence<N2>());
51+
}
52+
53+
template <size_t N>
54+
constexpr descr<N - 1> _(char const(&text)[N]) { return descr<N - 1>(text); }
55+
constexpr descr<0> _(char const(&)[1]) { return {}; }
56+
7057
template <size_t Rem, size_t... Digits> struct int_to_str : int_to_str<Rem/10, Rem%10, Digits...> { };
7158
template <size_t...Digits> struct int_to_str<0, Digits...> {
72-
static constexpr auto digits = descr<sizeof...(Digits), 0>({ ('0' + Digits)..., '\0' }, { nullptr });
59+
static constexpr auto digits = descr<sizeof...(Digits)>(('0' + Digits)...);
7360
};
7461

7562
// Ternary description (like std::conditional)
76-
template <bool B, size_t Size1, size_t Size2>
77-
constexpr enable_if_t<B, descr<Size1 - 1, 0>> _(char const(&text1)[Size1], char const(&)[Size2]) {
63+
template <bool B, size_t N1, size_t N2>
64+
constexpr enable_if_t<B, descr<N1 - 1>> _(char const(&text1)[N1], char const(&)[N2]) {
7865
return _(text1);
7966
}
80-
template <bool B, size_t Size1, size_t Size2>
81-
constexpr enable_if_t<!B, descr<Size2 - 1, 0>> _(char const(&)[Size1], char const(&text2)[Size2]) {
67+
template <bool B, size_t N1, size_t N2>
68+
constexpr enable_if_t<!B, descr<N2 - 1>> _(char const(&)[N1], char const(&text2)[N2]) {
8269
return _(text2);
8370
}
84-
template <bool B, size_t SizeA1, size_t SizeA2, size_t SizeB1, size_t SizeB2>
85-
constexpr enable_if_t<B, descr<SizeA1, SizeA2>> _(descr<SizeA1, SizeA2> d, descr<SizeB1, SizeB2>) { return d; }
86-
template <bool B, size_t SizeA1, size_t SizeA2, size_t SizeB1, size_t SizeB2>
87-
constexpr enable_if_t<!B, descr<SizeB1, SizeB2>> _(descr<SizeA1, SizeA2>, descr<SizeB1, SizeB2> d) { return d; }
71+
72+
template <bool B, typename T1, typename T2>
73+
constexpr enable_if_t<B, T1> _(const T1 &d, const T2 &) { return d; }
74+
template <bool B, typename T1, typename T2>
75+
constexpr enable_if_t<!B, T2> _(const T1 &, const T2 &d) { return d; }
8876

8977
template <size_t Size> auto constexpr _() -> decltype(int_to_str<Size / 10, Size % 10>::digits) {
9078
return int_to_str<Size / 10, Size % 10>::digits;
9179
}
9280

93-
template <typename Type> constexpr descr<1, 1> _() {
94-
return descr<1, 1>({ '%', '\0' }, { &typeid(Type), nullptr });
95-
}
81+
template <typename Type> constexpr descr<1, Type> _() { return {'%'}; }
9682

97-
constexpr descr<0, 0> concat() { return _(""); }
83+
constexpr descr<0> concat() { return {}; }
9884

99-
template <size_t Size1, size_t Size2>
100-
constexpr descr<Size1, Size2> concat(descr<Size1, Size2> descr) { return descr; }
85+
template <size_t N, typename... Ts>
86+
constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) { return descr; }
10187

102-
template <size_t Size1, size_t Size2, typename... Args>
103-
constexpr auto concat(descr<Size1, Size2> d, Args... args)
104-
-> decltype(descr<Size1 + 2, Size2>{} + concat(args...)) {
88+
template <size_t N, typename... Ts, typename... Args>
89+
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
90+
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
10591
return d + _(", ") + concat(args...);
10692
}
10793

108-
template <size_t Size1, size_t Size2>
109-
constexpr descr<Size1 + 2, Size2> type_descr(descr<Size1, Size2> descr) {
94+
template <size_t N, typename... Ts>
95+
constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) {
11096
return _("{") + descr + _("}");
11197
}
11298

include/pybind11/numpy.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -939,8 +939,8 @@ template <typename T>
939939
struct format_descriptor<T, detail::enable_if_t<detail::array_info<T>::is_array>> {
940940
static std::string format() {
941941
using detail::_;
942-
constexpr auto extents = _("(") + detail::array_info<T>::extents + _(")");
943-
return extents.text() + format_descriptor<detail::remove_all_extents_t<T>>::format();
942+
static constexpr auto extents = _("(") + detail::array_info<T>::extents + _(")");
943+
return extents.text + format_descriptor<detail::remove_all_extents_t<T>>::format();
944944
}
945945
};
946946

include/pybind11/pybind11.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class cpp_function : public function {
9292
/// Special internal constructor for functors, lambda functions, etc.
9393
template <typename Func, typename Return, typename... Args, typename... Extra>
9494
void initialize(Func &&f, Return (*)(Args...), const Extra&... extra) {
95-
95+
using namespace detail;
9696
struct capture { detail::remove_reference_t<Func> f; };
9797

9898
/* Store the function including any extra state it might have (e.g. a lambda capture object) */
@@ -163,11 +163,11 @@ class cpp_function : public function {
163163
detail::process_attributes<Extra...>::init(extra..., rec);
164164

165165
/* Generate a readable signature describing the function's arguments and return value types */
166-
using detail::descr; using detail::_;
167-
constexpr auto signature = _("(") + cast_in::arg_names + _(") -> ") + cast_out::name;
166+
static constexpr auto signature = _("(") + cast_in::arg_names + _(") -> ") + cast_out::name;
167+
PYBIND11_DESCR_CONSTEXPR auto types = decltype(signature)::types();
168168

169169
/* Register the function with Python from generic (non-templated) code */
170-
initialize_generic(rec, signature.text(), signature.types(), sizeof...(Args));
170+
initialize_generic(rec, signature.text, types.data(), sizeof...(Args));
171171

172172
if (cast_in::has_args) rec->has_args = true;
173173
if (cast_in::has_kwargs) rec->has_kwargs = true;

0 commit comments

Comments
 (0)