From 56613945aea9f2786ddf004cf17a770dc5270c16 Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Sun, 2 Jul 2017 12:52:00 +0200 Subject: [PATCH] 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. --- README.md | 5 +- docs/changelog.rst | 6 +- docs/faq.rst | 30 --------- docs/intro.rst | 5 +- include/pybind11/detail/descr.h | 114 ++++++++++++++------------------ include/pybind11/numpy.h | 4 +- include/pybind11/pybind11.h | 8 +-- 7 files changed, 65 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 447788240..04568aced 100644 --- a/README.md +++ b/README.md @@ -87,9 +87,8 @@ In addition to the core functionality, pybind11 provides some extra goodies: [reported](http://graylab.jhu.edu/RosettaCon2016/PyRosetta-4.pdf) a binary size reduction of **5.4x** and compile time reduction by **5.8x**. -- When supported by the compiler, two new C++14 features (relaxed constexpr and - return value deduction) are used to precompute function signatures at compile - time, leading to smaller binaries. +- Function signatures are precomputed at compile time (using ``constexpr``), + leading to smaller binaries. - With little extra effort, C++ types can be pickled and unpickled similar to regular Python objects. diff --git a/docs/changelog.rst b/docs/changelog.rst index 1ca501d15..8b7047df4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,7 +9,11 @@ Starting with version 1.8.0, pybind11 releases use a `semantic versioning v2.3.0 (Not yet released) ----------------------------------------------------- -* TBD +* Significantly reduced module binary size (10-20%) when compiled in C++11 mode + with GCC/Clang, or in any mode with MSVC. Function signatures are now always + precomputed at compile time (this was previously only available in C++14 mode + for non-MSVC compilers). + `#934 `_. v2.2.1 (September 14, 2017) ----------------------------------------------------- diff --git a/docs/faq.rst b/docs/faq.rst index 8f33eb014..d44a272bb 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -228,36 +228,6 @@ In addition to decreasing binary size, ``-fvisibility=hidden`` also avoids potential serious issues when loading multiple modules and is required for proper pybind operation. See the previous FAQ entry for more details. -Another aspect that can require a fair bit of code are function signature -descriptions. pybind11 automatically generates human-readable function -signatures for docstrings, e.g.: - -.. code-block:: none - - | __init__(...) - | __init__(*args, **kwargs) - | Overloaded function. - | - | 1. __init__(example.Example1) -> NoneType - | - | Docstring for overload #1 goes here - | - | 2. __init__(example.Example1, int) -> NoneType - | - | Docstring for overload #2 goes here - | - | 3. __init__(example.Example1, example.Example1) -> NoneType - | - | Docstring for overload #3 goes here - - -In C++11 mode, these are generated at run time using string concatenation, -which can amount to 10-20% of the size of the resulting binary. If you can, -enable C++14 language features (using ``-std=c++14`` for GCC/Clang), in which -case signatures are efficiently pre-generated at compile time. Unfortunately, -Visual Studio's C++14 support (``constexpr``) is not good enough as of April -2016, so it always uses the more expensive run-time approach. - Working with ancient Visual Studio 2009 builds on Windows ========================================================= diff --git a/docs/intro.rst b/docs/intro.rst index 2149c18db..3e9420ec8 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -77,9 +77,8 @@ In addition to the core functionality, pybind11 provides some extra goodies: of `PyRosetta`_, an enormous Boost.Python binding project, reported a binary size reduction of **5.4x** and compile time reduction by **5.8x**. -- When supported by the compiler, two new C++14 features (relaxed constexpr and - return value deduction) are used to precompute function signatures at compile - time, leading to smaller binaries. +- Function signatures are precomputed at compile time (using ``constexpr``), + leading to smaller binaries. - With little extra effort, C++ types can be pickled and unpickled similar to regular Python objects. diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 51e85d805..8d404e534 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -14,99 +14,85 @@ NAMESPACE_BEGIN(PYBIND11_NAMESPACE) NAMESPACE_BEGIN(detail) +#if !defined(_MSC_VER) +# define PYBIND11_DESCR_CONSTEXPR static constexpr +#else +# define PYBIND11_DESCR_CONSTEXPR const +#endif + /* Concatenate type signatures at compile time */ -template class descr { - template friend class descr; -public: - constexpr descr() = default; +template +struct descr { + char text[N + 1]; - constexpr descr(char const (&text) [Size1+1], const std::type_info * const (&types)[Size2+1]) - : descr(text, types, - make_index_sequence(), - make_index_sequence()) { } + constexpr descr() : text{'\0'} { } + constexpr descr(char const (&s)[N+1]) : descr(s, make_index_sequence()) { } - constexpr const char *text() const { return m_text; } - constexpr const std::type_info * const * types() const { return m_types; } + template + constexpr descr(char const (&s)[N+1], index_sequence) : text{s[Is]..., '\0'} { } - template - constexpr descr operator+(const descr &other) const { - return concat(other, - make_index_sequence(), - make_index_sequence(), - make_index_sequence(), - make_index_sequence()); + template + constexpr descr(char c, Chars... cs) : text{c, static_cast(cs)..., '\0'} { } + + static constexpr std::array types() { + return {{&typeid(Ts)..., nullptr}}; } - -protected: - template - constexpr descr( - char const (&text) [Size1+1], - const std::type_info * const (&types) [Size2+1], - index_sequence, index_sequence) - : m_text{text[Indices1]..., '\0'}, - m_types{types[Indices2]..., nullptr } {} - - template - constexpr descr - concat(const descr &other, - index_sequence, index_sequence, - index_sequence, index_sequence) const { - return descr( - { m_text[Indices1]..., other.m_text[OtherIndices1]..., '\0' }, - { m_types[Indices2]..., other.m_types[OtherIndices2]..., nullptr } - ); - } - -protected: - char m_text[Size1 + 1]; - const std::type_info * m_types[Size2 + 1]; }; -template constexpr descr _(char const(&text)[Size]) { - return descr(text, { nullptr }); +template +constexpr descr plus_impl(const descr &a, const descr &b, + index_sequence, index_sequence) { + return {a.text[Is1]..., b.text[Is2]...}; } +template +constexpr descr operator+(const descr &a, const descr &b) { + return plus_impl(a, b, make_index_sequence(), make_index_sequence()); +} + +template +constexpr descr _(char const(&text)[N]) { return descr(text); } +constexpr descr<0> _(char const(&)[1]) { return {}; } + template struct int_to_str : int_to_str { }; template struct int_to_str<0, Digits...> { - static constexpr auto digits = descr({ ('0' + Digits)..., '\0' }, { nullptr }); + static constexpr auto digits = descr(('0' + Digits)...); }; // Ternary description (like std::conditional) -template -constexpr enable_if_t> _(char const(&text1)[Size1], char const(&)[Size2]) { +template +constexpr enable_if_t> _(char const(&text1)[N1], char const(&)[N2]) { return _(text1); } -template -constexpr enable_if_t> _(char const(&)[Size1], char const(&text2)[Size2]) { +template +constexpr enable_if_t> _(char const(&)[N1], char const(&text2)[N2]) { return _(text2); } -template -constexpr enable_if_t> _(descr d, descr) { return d; } -template -constexpr enable_if_t> _(descr, descr d) { return d; } + +template +constexpr enable_if_t _(const T1 &d, const T2 &) { return d; } +template +constexpr enable_if_t _(const T1 &, const T2 &d) { return d; } template auto constexpr _() -> decltype(int_to_str::digits) { return int_to_str::digits; } -template constexpr descr<1, 1> _() { - return descr<1, 1>({ '%', '\0' }, { &typeid(Type), nullptr }); -} +template constexpr descr<1, Type> _() { return {'%'}; } -constexpr descr<0, 0> concat() { return _(""); } +constexpr descr<0> concat() { return {}; } -template -constexpr descr concat(descr descr) { return descr; } +template +constexpr descr concat(const descr &descr) { return descr; } -template -constexpr auto concat(descr d, Args... args) - -> decltype(descr{} + concat(args...)) { +template +constexpr auto concat(const descr &d, const Args &...args) + -> decltype(std::declval>() + concat(args...)) { return d + _(", ") + concat(args...); } -template -constexpr descr type_descr(descr descr) { +template +constexpr descr type_descr(const descr &descr) { return _("{") + descr + _("}"); } diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 593a796ff..3755e8907 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -941,8 +941,8 @@ template struct format_descriptor::is_array>> { static std::string format() { using detail::_; - constexpr auto extents = _("(") + detail::array_info::extents + _(")"); - return extents.text() + format_descriptor>::format(); + static constexpr auto extents = _("(") + detail::array_info::extents + _(")"); + return extents.text + format_descriptor>::format(); } }; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 9ac4636d6..90e12c56f 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -92,7 +92,7 @@ protected: /// Special internal constructor for functors, lambda functions, etc. template void initialize(Func &&f, Return (*)(Args...), const Extra&... extra) { - + using namespace detail; struct capture { detail::remove_reference_t f; }; /* Store the function including any extra state it might have (e.g. a lambda capture object) */ @@ -163,11 +163,11 @@ protected: detail::process_attributes::init(extra..., rec); /* Generate a readable signature describing the function's arguments and return value types */ - using detail::descr; using detail::_; - constexpr auto signature = _("(") + cast_in::arg_names + _(") -> ") + cast_out::name; + static constexpr auto signature = _("(") + cast_in::arg_names + _(") -> ") + cast_out::name; + PYBIND11_DESCR_CONSTEXPR auto types = decltype(signature)::types(); /* Register the function with Python from generic (non-templated) code */ - initialize_generic(rec, signature.text(), signature.types(), sizeof...(Args)); + initialize_generic(rec, signature.text, types.data(), sizeof...(Args)); if (cast_in::has_args) rec->has_args = true; if (cast_in::has_kwargs) rec->has_kwargs = true;