From e0db595760ece2f412320b0336e9f57399e4cd4a Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 2 Feb 2019 15:27:39 +0200 Subject: [PATCH] Faster http::string_to_field: * Optimize hash calculation and equality for field strings Experiments show the digest and equality functions taking the most time. Optimizing L1 cache usage did not show significant improvements. --- CHANGELOG.md | 6 + include/boost/beast/http/impl/field.ipp | 162 ++++++++++++------------ 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0e6c9f8..748fe1cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Version 209: + +* Faster http::string_to_field + +-------------------------------------------------------------------------------- + Version 208: * Add get_lowest_layer free function diff --git a/include/boost/beast/http/impl/field.ipp b/include/boost/beast/http/impl/field.ipp index b61a0ba8..4c77f2ce 100644 --- a/include/boost/beast/http/impl/field.ipp +++ b/include/boost/beast/http/impl/field.ipp @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace boost { @@ -28,58 +29,66 @@ struct field_table using array_type = std::array; - struct hash + // Strings are converted to lowercase + static + std::uint32_t + digest(string_view s) { - std::size_t - operator()(string_view s) const + std::uint32_t r = 0; + std::size_t n = s.size(); + unsigned char const* p =reinterpret_cast< + unsigned char const*>(s.data()); + while(n >= 4) { - auto const n = s.size(); - return - beast::detail::ascii_tolower(s[0]) * - beast::detail::ascii_tolower(s[n/2]) ^ - beast::detail::ascii_tolower(s[n-1]); // hist[] = 331, 10, max_load_factor = 0.15f + std::uint32_t v; + std::memcpy(&v, p, 4); + r = r * 5 + ( v | 0x20202020 ); + p += 4; + n -= 4; } - }; + while( n > 0 ) + { + r = r * 5 + ( *p | 0x20 ); + ++p; + --n; + } + return r; + } - struct iequal + // This comparison is case-insensitive, and the + // strings must contain only valid http field characters. + static + bool + equals(string_view lhs, string_view rhs) { - // assumes inputs have equal length - bool - operator()( - string_view lhs, - string_view rhs) const + using Int = std::uint32_t; // or std::size_t + auto n = lhs.size(); + if(n != rhs.size()) + return false; + auto p1 = lhs.data(); + auto p2 = rhs.data(); + auto constexpr S = sizeof(Int); + auto constexpr Mask = static_cast( + 0xDFDFDFDFDFDFDFDF & ~Int{0}); + for(; n >= S; p1 += S, p2 += S, n -= S) { - auto p1 = lhs.data(); - auto p2 = rhs.data(); - auto pend = p1 + lhs.size(); - char a, b; - while(p1 < pend) - { - a = *p1++; - b = *p2++; - if(a != b) - goto slow; - } - return true; - - while(p1 < pend) - { - slow: - if( beast::detail::ascii_tolower(a) != - beast::detail::ascii_tolower(b)) - return false; - a = *p1++; - b = *p2++; - } - return true; + Int v1, v2; + std::memcpy( &v1, p1, S ); + std::memcpy( &v2, p2, S ); + if((v1 ^ v2) & Mask) + return false; } - }; - - using map_type = std::unordered_map< - string_view, field, hash, iequal>; + for(; n; ++p1, ++p2, --n) + if(( *p1 ^ *p2) & 0xDF) + return false; + return true; + } array_type by_name_; - std::vector by_size_; + + enum { N = 5155 }; + unsigned char map_[ N ][ 2 ] = {}; + /* From: @@ -442,58 +451,43 @@ struct field_table "Xref" }}) { - // find the longest field length - std::size_t high = 0; - for(auto const& s : by_name_) - if(high < s.size()) - high = s.size(); - // build by_size map - // skip field::unknown - by_size_.resize(high + 1); - for(auto& map : by_size_) - map.max_load_factor(.15f); - for(std::size_t i = 1; - i < by_name_.size(); ++i) + for(std::size_t i = 1, n = 256; i < n; ++i) { - auto const& s = by_name_[i]; - by_size_[s.size()].emplace( - s, static_cast(i)); + auto sv = by_name_[ i ]; + auto h = digest(sv); + auto j = h % N; + BOOST_ASSERT(map_[j][0] == 0); + map_[j][0] = static_cast(i); } -#if 0 - // This snippet calculates the performance - // of the hash function and map settings + for(std::size_t i = 256, n = by_name_.size(); i < n; ++i) { - std::vector hist; - for(auto const& map : by_size_) - { - for(std::size_t i = 0; i < map.bucket_count(); ++i) - { - auto const n = map.bucket_size(i); - if(n > 0) - { - if(hist.size() < n) - hist.resize(n); - ++hist[n-1]; - } - } - } + auto sv = by_name_[i]; + auto h = digest(sv); + auto j = h % N; + BOOST_ASSERT(map_[j][1] == 0); + map_[j][1] = static_cast(i - 255); } -#endif } field string_to_field(string_view s) const { - if(s.size() >= by_size_.size()) + auto h = digest(s); + auto j = h % N; + int i = map_[j][0]; + string_view s2 = by_name_[i]; + if(i != 0 && equals(s, s2)) + return static_cast(i); + i = map_[j][1]; + if(i == 0) return field::unknown; - auto const& map = by_size_[s.size()]; - if(map.empty()) - return field::unknown; - auto it = map.find(s); - if(it == map.end()) - return field::unknown; - return it->second; + i += 255; + s2 = by_name_[i]; + + if(equals(s, s2)) + return static_cast(i); + return field::unknown; } //