From 518ae23383a851fc5b959f59f8659201e293bdf3 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 22 Jun 2017 16:49:46 -0700 Subject: [PATCH] string_param optimizations --- CHANGELOG.md | 6 + include/beast/core/detail/static_ostream.hpp | 24 ++-- include/beast/core/detail/static_string.hpp | 60 ++++++++- include/beast/core/impl/static_string.ipp | 20 ++- include/beast/core/impl/string_param.ipp | 104 +++++++++++++++ include/beast/core/string_param.hpp | 130 ++++++++----------- include/beast/http/impl/fields.ipp | 12 +- test/core/string_param.cpp | 6 +- 8 files changed, 263 insertions(+), 99 deletions(-) create mode 100644 include/beast/core/impl/string_param.ipp diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa140f0..1fe50238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Version 66: + +* string_param optimizations + +-------------------------------------------------------------------------------- + Version 65: * Enable narrowing warning on msvc cmake diff --git a/include/beast/core/detail/static_ostream.hpp b/include/beast/core/detail/static_ostream.hpp index f0f6b3d1..99f4282d 100644 --- a/include/beast/core/detail/static_ostream.hpp +++ b/include/beast/core/detail/static_ostream.hpp @@ -27,17 +27,20 @@ class static_ostream_buffer using traits_type = typename std::basic_streambuf::traits_type; - char buf_[128]; - std::string s_; + char* data_; + std::size_t size_; std::size_t len_ = 0; + std::string s_; public: static_ostream_buffer(static_ostream_buffer&&) = delete; static_ostream_buffer(static_ostream_buffer const&) = delete; - static_ostream_buffer() + static_ostream_buffer(char* data, std::size_t size) + : data_(data) + , size_(size) { - this->setp(buf_, buf_ + sizeof(buf_) - 1); + this->setp(data_, data_ + size - 1); } ~static_ostream_buffer() noexcept @@ -49,7 +52,7 @@ public: { if(! s_.empty()) return {s_.data(), len_}; - return {buf_, len_}; + return {data_, len_}; } int_type @@ -81,17 +84,17 @@ private: { static auto const growth_factor = 1.5; - if(len_ < sizeof(buf_) - 1) + if(len_ < size_ - 1) { - this->setp(&buf_[len_], - &buf_[sizeof(buf_) - 2]); + this->setp( + data_ + len_, data_ + size_ - 2); return; } if(s_.empty()) { s_.resize(static_cast( growth_factor * len_)); - Traits::copy(&s_[0], buf_, len_); + Traits::copy(&s_[0], data_, len_); } else { @@ -115,8 +118,9 @@ class static_ostream : public std::basic_ostream static_ostream_buffer osb_; public: - static_ostream() + static_ostream(char* data, std::size_t size) : std::basic_ostream(&this->osb_) + , osb_(data, size) { imbue(std::locale::classic()); } diff --git a/include/beast/core/detail/static_string.hpp b/include/beast/core/detail/static_string.hpp index cb99fe94..2f8a5810 100644 --- a/include/beast/core/detail/static_string.hpp +++ b/include/beast/core/detail/static_string.hpp @@ -9,6 +9,7 @@ #define BEAST_DETAIL_STATIC_STRING_HPP #include +#include #include #include @@ -59,13 +60,68 @@ lexicographical_compare( } // Maximum number of characters in the decimal -// representation of a binary number +// representation of a binary number. This includes +// the potential minus sign. +// inline std::size_t constexpr max_digits(std::size_t bytes) { return static_cast( - bytes * 2.41) + 1; + bytes * 2.41) + 1 + 1; +} + +template +CharT* +raw_to_string( + CharT* buf, Integer x, std::true_type) +{ + if(x == 0) + { + Traits::assign(*--buf, '0'); + return buf; + } + if(x < 0) + { + x = -x; + for(;x > 0; x /= 10) + Traits::assign(*--buf , + "0123456789"[x % 10]); + Traits::assign(*--buf, '-'); + return buf; + } + for(;x > 0; x /= 10) + Traits::assign(*--buf , + "0123456789"[x % 10]); + return buf; +} + +template +CharT* +raw_to_string( + CharT* buf, Integer x, std::false_type) +{ + if(x == 0) + { + *--buf = '0'; + return buf; + } + for(;x > 0; x /= 10) + Traits::assign(*--buf , + "0123456789"[x % 10]); + return buf; +} + +template< + class CharT, + class Integer, + class Traits = std::char_traits> +CharT* +raw_to_string(CharT* last, std::size_t size, Integer i) +{ + BOOST_ASSERT(size >= max_digits(sizeof(Integer))); + return raw_to_string( + last, i, std::is_signed{}); } } // detail diff --git a/include/beast/core/impl/static_string.ipp b/include/beast/core/impl/static_string.ipp index 9dd4391f..6bab53b5 100644 --- a/include/beast/core/impl/static_string.ipp +++ b/include/beast/core/impl/static_string.ipp @@ -8,6 +8,7 @@ #ifndef BEAST_IMPL_STATIC_STRING_IPP #define BEAST_IMPL_STATIC_STRING_IPP +#include #include #include @@ -534,12 +535,12 @@ assign_char(CharT, std::false_type) -> namespace detail { template -static_string<1+max_digits(sizeof(Integer))> +static_string to_static_string(Integer x, std::true_type) { if(x == 0) return {'0'}; - static_string<1 + detail::max_digits( + static_string s; if(x < 0) { @@ -593,10 +594,19 @@ template static_string to_static_string(Integer x) { + using CharT = char; + using Traits = std::char_traits; BOOST_STATIC_ASSERT(std::is_integral::value); - return detail::to_static_string( - x, std::integral_constant::value>{}); + char buf[detail::max_digits(sizeof(Integer))]; + auto last = buf + sizeof(buf); + auto it = detail::raw_to_string< + CharT, Integer, Traits>(last, sizeof(buf), x); + static_string s; + s.resize(static_cast(last - it)); + auto p = s.data(); + while(it < last) + Traits::assign(*p++, *it++); + return s; } } // beast diff --git a/include/beast/core/impl/string_param.ipp b/include/beast/core/impl/string_param.ipp new file mode 100644 index 00000000..8bcccca4 --- /dev/null +++ b/include/beast/core/impl/string_param.ipp @@ -0,0 +1,104 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_STRING_PARAM_IPP +#define BEAST_IMPL_STRING_PARAM_IPP + +namespace beast { + +template +typename std::enable_if< + std::is_integral::value>::type +string_param:: +print(T const& t) +{ + auto const last = buf_ + sizeof(buf_); + auto const it = detail::raw_to_string< + char, T, std::char_traits>( + last, sizeof(buf_), t); + sv_ = {it, static_cast( + last - it)}; +} + +template +typename std::enable_if< + ! std::is_integral::value && + ! std::is_convertible::value +>::type +string_param:: +print(T const& t) +{ + os_.emplace(buf_, sizeof(buf_)); + *os_ << t; + os_->flush(); + sv_ = os_->str(); +} + +inline +void +string_param:: +print(string_view const& sv) +{ + sv_ = sv; +} + +template +typename std::enable_if< + std::is_integral::value>::type +string_param:: +print_1(T const& t) +{ + char buf[detail::max_digits(sizeof(T))]; + auto const last = buf + sizeof(buf); + auto const it = detail::raw_to_string< + char, T, std::char_traits>( + last, sizeof(buf), t); + *os_ << string_view{it, + static_cast(last - it)}; +} + +template +typename std::enable_if< + ! std::is_integral::value>::type +string_param:: +print_1(T const& t) +{ + *os_ << t; +} + +template +void +string_param:: +print_n(T0 const& t0, TN const&... tn) +{ + print_1(t0); + print_n(tn...); +} + +template +void +string_param:: +print(T0 const& t0, T1 const& t1, TN const&... tn) +{ + os_.emplace(buf_, sizeof(buf_)); + print_1(t0); + print_1(t1); + print_n(tn...); + os_->flush(); + sv_ = os_->str(); +} + +template +string_param:: +string_param(Args const&... args) +{ + print(args...); +} + +} // beast + +#endif diff --git a/include/beast/core/string_param.hpp b/include/beast/core/string_param.hpp index be16c54e..dedd060a 100644 --- a/include/beast/core/string_param.hpp +++ b/include/beast/core/string_param.hpp @@ -10,8 +10,10 @@ #include #include +#include #include #include +#include namespace beast { @@ -26,34 +28,47 @@ namespace beast { */ class string_param { - template - struct is_streamable : std::false_type{}; - -#if ! BEAST_DOXYGEN - template - struct is_streamable() << std::declval() - )>> : std::true_type {}; -#endif - - template - void - assign(T const& t, std::true_type) - { - sv_ = t; - } - - template - void - assign(T const& t, std::false_type) - { - os_ << t; - os_.flush(); - sv_ = os_.str(); - } - - detail::static_ostream os_; string_view sv_; + char buf_[128]; + boost::optional os_; + + template + typename std::enable_if< + std::is_integral::value>::type + print(T const&); + + template + typename std::enable_if< + ! std::is_integral::value && + ! std::is_convertible::value + >::type + print(T const&); + + void + print(string_view const&); + + template + typename std::enable_if< + std::is_integral::value>::type + print_1(T const&); + + template + typename std::enable_if< + ! std::is_integral::value>::type + print_1(T const&); + + void + print_n() + { + } + + template + void + print_n(T0 const&, TN const&...); + + template + void + print(T0 const&, T1 const&, TN const&...); public: /// Copy constructor (disallowed) @@ -64,62 +79,31 @@ public: /** Constructor - This function constructs a string from the specified - parameter. - - If @ref string_view is constructible from `T` then - the complexity is O(1), and no dynamic allocation - takes place. + This function constructs a string as if by concatenating + the result of streaming each argument in order into an + output stream. It is used as a notational convenience + at call sites which expect a parameter with the semantics + of a @ref string_view. - Otherwise, the argument is converted to a string - as if by a call to `boost::lexical_cast(t)`. - An attempt is made to store this string in a reasonably - sized buffer inside the class, to avoid dynamic allocation. + The implementation uses a small, internal static buffer + to avoid memory allocations especially for the case where + the list of arguments to be converted consists of a single + integral type. - If the argument could not be converted given the space - of the class buffer, a final attempt is made to convert - the argument to a `std::string`. This attempt may cause - a memory allocation. - - @param t The argument to convert to string. - - @note This function participates in overload resolution - only if `T` is convertible to @ref string_view, or if - `operator<<(std::ostream&, T)` is defined. + @param args One or more arguments to convert */ -#if BEAST_DOXYGEN - template -#else - template::value || - is_streamable::value - >::type> -#endif - string_param(T const& t); + template + string_param(Args const&... args); - /// Conversion to @ref string_view for the converted argument. - string_view const - str() const - { - return sv_; - } - - /// Conversion to @ref string_view operator + /// Implicit conversion to @ref string_view operator string_view const() const { return sv_; } }; -#if ! BEAST_DOXYGEN -template -string_param:: -string_param(T const& t) -{ - assign(t, std::is_convertible{}); -} -#endif - } // beast +#include + #endif diff --git a/include/beast/http/impl/fields.ipp b/include/beast/http/impl/fields.ipp index 0cb9db8f..474510a9 100644 --- a/include/beast/http/impl/fields.ipp +++ b/include/beast/http/impl/fields.ipp @@ -635,8 +635,8 @@ basic_fields:: insert(field name, string_view sname, string_param const& value) { - auto& e = new_element( - name, sname, value.str()); + auto& e = new_element(name, sname, + static_cast(value)); auto const before = set_.upper_bound(sname, key_compare{}); if(before == set_.begin()) @@ -666,8 +666,8 @@ basic_fields:: set(field name, string_param const& value) { BOOST_ASSERT(name != field::unknown); - set_element(new_element(name, - to_string(name), value.str())); + set_element(new_element(name, to_string(name), + static_cast(value))); } template @@ -676,8 +676,8 @@ basic_fields:: set(string_view sname, string_param const& value) { set_element(new_element( - string_to_field(sname), - sname, value.str())); + string_to_field(sname), sname, + static_cast(value))); } template diff --git a/test/core/string_param.cpp b/test/core/string_param.cpp index 81193a53..4ccfc9a0 100644 --- a/test/core/string_param.cpp +++ b/test/core/string_param.cpp @@ -18,12 +18,10 @@ class string_param_test : public unit_test::suite public: struct nop {}; - static_assert(! std::is_constructible::value, ""); - void check(string_param const& v, string_view s) { - BEAST_EXPECT(v.str() == s); + BEAST_EXPECT(static_cast(v) == s); } class repeater @@ -56,6 +54,8 @@ public: check(123, "123"); check(1234, "1234"); check(12345, "12345"); + check({"a", "b"}, "ab"); + check({1, 2, 3}, "123"); } void