diff options
| author | Rye <rye@alchemyviewer.org> | 2026-01-11 13:38:37 -0500 |
|---|---|---|
| committer | Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> | 2026-01-21 22:07:08 +0200 |
| commit | 4de1ba776c60e935e8cf069c1aef104ee2b07559 (patch) | |
| tree | f2019f5a9f53f2921dbfbbc576c7ec4e233237ba /indra | |
| parent | 28fee038e0e14e0538eefade38961e9ae1665d81 (diff) | |
Heavily reduce temporary allocations during LLSD parsing operations by utilizing moves and reducing temporary allocations
Diffstat (limited to 'indra')
| -rw-r--r-- | indra/llcommon/llsd.cpp | 135 | ||||
| -rw-r--r-- | indra/llcommon/llsd.h | 37 | ||||
| -rw-r--r-- | indra/llcommon/llsdjson.cpp | 4 | ||||
| -rw-r--r-- | indra/llcommon/llsdserialize.cpp | 52 | ||||
| -rw-r--r-- | indra/llcommon/llsdserialize_xml.cpp | 49 |
5 files changed, 226 insertions, 51 deletions
diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index 77fe545c3f..6bc2841081 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -39,6 +39,9 @@ #include <limits> +#include <boost/iostreams/device/array.hpp> +#include <boost/iostreams/stream.hpp> + // Defend against a caller forcibly passing a negative number into an unsigned // size_t index param inline @@ -103,6 +106,9 @@ protected: U32 mUseCount; public: + static void destruct(Impl*& var); + ///< safely decrement or destroy var + static void reset(Impl*& var, Impl* impl); ///< safely set var to refer to the new impl (possibly shared) @@ -166,7 +172,7 @@ public: virtual const LLSD& ref(size_t) const { return undef(); } virtual LLSD::map_const_iterator beginMap() const { return endMap(); } - virtual LLSD::map_const_iterator endMap() const { static const std::map<String, LLSD> empty; return empty.end(); } + virtual LLSD::map_const_iterator endMap() const { static const LLSD::llsd_map_t empty; return empty.end(); } virtual LLSD::array_const_iterator beginArray() const { return endArray(); } virtual LLSD::array_const_iterator endArray() const { static const std::vector<LLSD> empty; return empty.end(); } @@ -346,7 +352,7 @@ namespace LLSD::Real ImplString::asReal() const { F64 v = 0.0; - std::istringstream i_stream(mValue); + boost::iostreams::stream<boost::iostreams::array_source> i_stream(mValue.data(), mValue.size()); i_stream >> v; // we would probably like to ignore all trailing whitespace as @@ -431,7 +437,7 @@ namespace class ImplMap final : public LLSD::Impl { private: - typedef std::map<LLSD::String, LLSD, std::less<>> DataMap; + using DataMap = LLSD::llsd_map_t; DataMap mData; @@ -457,7 +463,7 @@ namespace << it.second.asXMLRPCValue() << "</member>"; } os << "</struct>"; - return os.str(); + return std::move(os).str(); } virtual bool has(std::string_view) const; @@ -467,8 +473,13 @@ namespace using LLSD::Impl::ref; // Unhiding ref(size_t) virtual LLSD get(std::string_view) const; virtual LLSD getKeys() const; + void insert(std::string&& k, const LLSD& v); + void insert(std::string&& k, LLSD&& v); void insert(std::string_view k, const LLSD& v); + void insert(std::string_view k, LLSD&& v); virtual void erase(const LLSD::String&); + LLSD& ref(std::string&&); + virtual const LLSD& ref(std::string&&) const; LLSD& ref(std::string_view); virtual const LLSD& ref(std::string_view) const; @@ -525,18 +536,58 @@ namespace return keys; } + void ImplMap::insert(std::string&& k, const LLSD& v) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; + mData.emplace(std::move(k), v); + } + + void ImplMap::insert(std::string&& k, LLSD&& v) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; + mData.emplace(std::move(k), std::move(v)); + } + void ImplMap::insert(std::string_view k, const LLSD& v) { LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; mData.emplace(k, v); } + void ImplMap::insert(std::string_view k, LLSD&& v) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; + mData.emplace(k, std::move(v)); + } + void ImplMap::erase(const LLSD::String& k) { LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; mData.erase(k); } + LLSD& ImplMap::ref(std::string&& k) + { + DataMap::iterator i = mData.lower_bound(k); + if (i == mData.end() || mData.key_comp()(k, i->first)) + { + return mData.emplace_hint(i, std::make_pair(std::move(k), LLSD()))->second; + } + + return i->second; + } + + const LLSD& ImplMap::ref(std::string&& k) const + { + DataMap::const_iterator i = mData.lower_bound(k); + if (i == mData.end() || mData.key_comp()(k, i->first)) + { + return undef(); + } + + return i->second; + } + LLSD& ImplMap::ref(std::string_view k) { DataMap::iterator i = mData.lower_bound(k); @@ -598,7 +649,7 @@ namespace ImplArray(const DataVector& data) : mData(data) { } public: - ImplArray() { } + ImplArray() = default; virtual ImplArray& makeArray(Impl*&); @@ -615,7 +666,7 @@ namespace os << it.asXMLRPCValue(); } os << "</data></array>"; - return os.str(); + return std::move(os).str(); } using LLSD::Impl::get; // Unhiding get(LLSD::String) @@ -625,10 +676,13 @@ namespace virtual LLSD get(size_t) const; void set(size_t, const LLSD&); void insert(size_t, const LLSD&); + void insert(size_t, LLSD&&); LLSD& append(const LLSD&); + LLSD& append(LLSD&&); virtual void erase(size_t); LLSD& ref(size_t); virtual const LLSD& ref(size_t) const; + void reserve(size_t size) { mData.reserve(size); } LLSD::array_iterator beginArray() { return mData.begin(); } LLSD::array_iterator endArray() { return mData.end(); } @@ -690,12 +744,31 @@ namespace mData.insert(mData.begin() + index, v); } + void ImplArray::insert(size_t i, LLSD&& v) + { + NEGATIVE_EXIT(i); + DataVector::size_type index = i; + + if (index >= mData.size()) // tbd - sanity check limit for index ? + { + mData.resize(index + 1); + } + + mData.insert(mData.begin() + index, std::move(v)); + } + LLSD& ImplArray::append(const LLSD& v) { mData.push_back(v); return mData.back(); } + LLSD& ImplArray::append(LLSD&& v) + { + mData.push_back(std::move(v)); + return mData.back(); + } + void ImplArray::erase(size_t i) { NEGATIVE_EXIT(i); @@ -763,6 +836,14 @@ LLSD::Impl::~Impl() --sOutstandingCount; } +void LLSD::Impl::destruct(Impl*& var) +{ + if (var && var->mUseCount != STATIC_USAGE_COUNT && --var->mUseCount == 0) + { + delete var; + } +} + void LLSD::Impl::reset(Impl*& var, Impl* impl) { if (impl && impl->mUseCount != STATIC_USAGE_COUNT) @@ -961,7 +1042,7 @@ namespace LLSD::LLSD() : impl(0) { ALLOC_LLSD_OBJECT; } -LLSD::~LLSD() { FREE_LLSD_OBJECT; Impl::reset(impl, 0); } +LLSD::~LLSD() { FREE_LLSD_OBJECT; Impl::destruct(impl); } LLSD::LLSD(const LLSD& other) : impl(0) { ALLOC_LLSD_OBJECT; assign(other); } void LLSD::assign(const LLSD& other) { Impl::assign(impl, other.impl); } @@ -1037,13 +1118,31 @@ LLSD LLSD::emptyMap() bool LLSD::has(const std::string_view k) const { return safe(impl).has(k); } LLSD LLSD::get(const std::string_view k) const { return safe(impl).get(k); } LLSD LLSD::getKeys() const { return safe(impl).getKeys(); } +void LLSD::insert(std::string&& k, const LLSD& v) { makeMap(impl).insert(std::move(k), v); } +void LLSD::insert(std::string&& k, LLSD&& v) { makeMap(impl).insert(std::move(k), std::move(v)); } void LLSD::insert(std::string_view k, const LLSD& v) { makeMap(impl).insert(k, v); } +void LLSD::insert(std::string_view k, LLSD&& v) { makeMap(impl).insert(k, std::move(v)); } +LLSD& LLSD::with(std::string&& k, const LLSD& v) + { + makeMap(impl).insert(std::move(k), v); + return *this; + } +LLSD& LLSD::with(std::string&& k, LLSD&& v) + { + makeMap(impl).insert(std::move(k), std::move(v)); + return *this; + } LLSD& LLSD::with(std::string_view k, const LLSD& v) { makeMap(impl).insert(k, v); return *this; } +LLSD& LLSD::with(std::string_view k, LLSD&& v) + { + makeMap(impl).insert(k, std::move(v)); + return *this; + } void LLSD::erase(const String& k) { makeMap(impl).erase(k); } LLSD& LLSD::operator[](const std::string_view k) @@ -1051,6 +1150,13 @@ LLSD& LLSD::operator[](const std::string_view k) LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; return makeMap(impl).ref(k); } + +LLSD& LLSD::operator[](std::string&& k) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; + return makeMap(impl).ref(std::move(k)); +} + const LLSD& LLSD::operator[](const std::string_view k) const { LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; @@ -1064,18 +1170,33 @@ LLSD LLSD::emptyArray() return v; } +LLSD LLSD::emptyReservedArray(size_t size) +{ + LLSD v; + makeArray(v.impl).reserve(size); + return v; +} + size_t LLSD::size() const { return safe(impl).size(); } LLSD LLSD::get(Integer i) const { return safe(impl).get(i); } void LLSD::set(Integer i, const LLSD& v){ makeArray(impl).set(i, v); } +void LLSD::set(Integer i, LLSD&& v) { makeArray(impl).set(i, std::move(v)); } void LLSD::insert(Integer i, const LLSD& v) { makeArray(impl).insert(i, v); } +void LLSD::insert(Integer i, LLSD&& v) { makeArray(impl).insert(i, std::move(v)); } LLSD& LLSD::with(Integer i, const LLSD& v) { makeArray(impl).insert(i, v); return *this; } +LLSD& LLSD::with(Integer i, LLSD&& v) + { + makeArray(impl).insert(i, std::move(v)); + return *this; + } LLSD& LLSD::append(const LLSD& v) { return makeArray(impl).append(v); } +LLSD& LLSD::append(LLSD&& v) { return makeArray(impl).append(std::move(v)); } void LLSD::erase(Integer i) { makeArray(impl).erase(i); } LLSD& LLSD::operator[](size_t i) diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index d2b3548831..c62f2ef1ca 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -34,6 +34,7 @@ #include "stdtypes.h" #include "lldate.h" +#include "llstl.h" #include "lluri.h" #include "lluuid.h" @@ -321,11 +322,34 @@ public: bool has(const std::string_view) const; LLSD get(const std::string_view) const; LLSD getKeys() const; // Return an LLSD array with keys as strings + void insert(const char* k, const LLSD& v) + { + return insert(std::string_view(k), v); + } + void insert(const char* k , LLSD&& v) + { + return insert(std::string_view(k), std::move(v)); + } + void insert(std::string&&, const LLSD&); + void insert(std::string&&, LLSD&&); void insert(std::string_view, const LLSD&); + void insert(std::string_view, LLSD&&); void erase(const String&); + LLSD& with(const char* k, const LLSD& v) + { + return with(std::string_view(k), v); + } + LLSD& with(const char* k, LLSD&& v) + { + return with(std::string_view(k), std::move(v)); + } + LLSD& with(std::string&&, const LLSD&); + LLSD& with(std::string&&, LLSD&&); LLSD& with(std::string_view, const LLSD&); + LLSD& with(std::string_view, LLSD&&); LLSD& operator[](const std::string_view); + LLSD& operator[](std::string&&); LLSD& operator[](const char* c) { return c ? (*this)[std::string_view(c)] : *this; @@ -339,14 +363,22 @@ public: /** @name Array Values */ //@{ + // Allocate an empty array static LLSD emptyArray(); + // Allocate an array with internal storage reserved but not initialized like a std::vector + static LLSD emptyReservedArray(size_t size); + LLSD get(Integer) const; void set(Integer, const LLSD&); + void set(Integer, LLSD&&); void insert(Integer, const LLSD&); + void insert(Integer, LLSD&&); LLSD& append(const LLSD&); + LLSD& append(LLSD&&); void erase(Integer); LLSD& with(Integer, const LLSD&); + LLSD& with(Integer, LLSD&&); // accept size_t so we can index relative to size() const LLSD& operator[](size_t) const; @@ -366,8 +398,9 @@ public: //@{ size_t size() const; - typedef std::map<String, LLSD>::iterator map_iterator; - typedef std::map<String, LLSD>::const_iterator map_const_iterator; + using llsd_map_t = std::map<String, LLSD, std::less<>>; + typedef llsd_map_t::iterator map_iterator; + typedef llsd_map_t::const_iterator map_const_iterator; map_iterator beginMap(); map_iterator endMap(); diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index 655869a704..bb806f32eb 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -66,7 +66,7 @@ LLSD LlsdFromJson(const boost::json::value& val) const boost::json::array& array = val.as_array(); size_t size = array.size(); // allocate elements 0 .. (size() - 1) to avoid incremental allocation - if (! array.empty()) + if (!array.empty()) { result[size - 1] = LLSD(); } @@ -80,7 +80,7 @@ LLSD LlsdFromJson(const boost::json::value& val) result = LLSD::emptyMap(); for (const auto& element : val.as_object()) { - result[element.key()] = LlsdFromJson(element.value()); + result[std::string_view(element.key())] = LlsdFromJson(element.value()); } break; } diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 37af366a20..ee7f0b01cc 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -775,7 +775,8 @@ S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) c // There must be a value for every key, thus // child_count must be greater than 0. parse_count += count; - map.insert(name, child); + map.insert(std::move(name), std::move(child)); // Move as name will be filled on next iteration + name.clear(); } else { @@ -822,7 +823,7 @@ S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_dept else { parse_count += count; - array.append(child); + array.append(std::move(child)); } c = get(istr); } @@ -841,7 +842,7 @@ bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const auto count = deserialize_string(istr, value, mMaxBytesLeft); if(PARSE_FAILURE == count) return false; account(count); - data = value; + data = std::move(value); return true; } @@ -872,10 +873,10 @@ bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const if(len) { value.resize(len); - account(fullread(istr, (char *)&value[0], len)); + account(fullread(istr, (char*)value.data(), len)); } c = get(istr); // strip off the trailing double-quote - data = value; + data = std::move(value); } else if(0 == strncmp("b64", buf, 3)) { @@ -885,7 +886,7 @@ bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const std::stringstream coded_stream; get(istr, *(coded_stream.rdbuf()), '\"'); c = get(istr); - std::string encoded(coded_stream.str()); + std::string encoded(std::move(coded_stream).str()); S32 len = apr_base64_decode_len(encoded.c_str()); std::vector<U8> value; if(len) @@ -894,7 +895,7 @@ bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const len = apr_base64_decode_binary(&value[0], encoded.c_str()); value.resize(len); } - data = value; + data = std::move(value); } else if(0 == strncmp("b16", buf, 3)) { @@ -925,7 +926,7 @@ bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const // copy the data out of the byte buffer value.insert(value.end(), byte_buffer, write); } - data = value; + data = std::move(value); } else { @@ -1077,7 +1078,7 @@ S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) con } else { - data = value; + data = std::move(value); account(cnt); } if(istr.fail()) @@ -1094,7 +1095,7 @@ S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) con std::string value; if(parseString(istr, value)) { - data = value; + data = std::move(value); } else { @@ -1159,7 +1160,7 @@ S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) con value.resize(size); account(fullread(istr, (char*)&value[0], size)); } - data = value; + data = std::move(value); } if(istr.fail()) { @@ -1218,7 +1219,7 @@ S32 LLSDBinaryParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) con // There must be a value for every key, thus child_count // must be greater than 0. parse_count += child_count; - map.insert(name, child); + map.insert(std::move(name), std::move(child)); } else { @@ -1238,13 +1239,12 @@ S32 LLSDBinaryParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) con S32 LLSDBinaryParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const { - array = LLSD::emptyArray(); U32 value_nbo = 0; read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ S32 size = (S32)ntohl(value_nbo); - // *FIX: This would be a good place to reserve some space in the - // array... + // Preallocate array to avoid incremental allocation + array = LLSD::emptyReservedArray(size); S32 parse_count = 0; S32 count = 0; @@ -1260,7 +1260,7 @@ S32 LLSDBinaryParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) if(child_count) { parse_count += child_count; - array.append(child); + array.append(std::move(child)); } ++count; c = istr.peek(); @@ -1279,18 +1279,15 @@ bool LLSDBinaryParser::parseString( std::istream& istr, std::string& value) const { - // *FIX: This is memory inefficient. U32 value_nbo = 0; read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ S32 size = (S32)ntohl(value_nbo); if(mCheckLimits && (size > mMaxBytesLeft)) return false; if(size < 0) return false; - std::vector<char> buf; if(size) { - buf.resize(size); - account(fullread(istr, &buf[0], size)); - value.assign(buf.begin(), buf.end()); + value.resize(size); + account(fullread(istr, value.data(), size)); } return true; } @@ -1785,7 +1782,7 @@ llssize deserialize_string_delim( } } - value = write_buffer.str(); + value = std::move(write_buffer).str(); return count; } @@ -1806,15 +1803,12 @@ llssize deserialize_string_raw( { // We probably have a valid raw string. determine // the size, and read it. - // *FIX: This is memory inefficient. - auto len = strtol(buf + 1, NULL, 0); + auto len = strtol(buf + 1, nullptr, 0); if((max_bytes>0)&&(len>max_bytes)) return LLSDParser::PARSE_FAILURE; - std::vector<char> buf; if(len) { - buf.resize(len); - count += fullread(istr, (char *)&buf[0], len); - value.assign(buf.begin(), buf.end()); + value.resize(len); + count += fullread(istr, value.data(), len); } c = istr.get(); ++count; @@ -2170,7 +2164,7 @@ std::string zip_llsd(LLSD& data) return std::string(); } - std::string source = llsd_strm.str(); + std::string source = std::move(llsd_strm).str(); U8 out[CHUNK]; diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp index 6396caf8d5..c28efa3aad 100644 --- a/indra/llcommon/llsdserialize_xml.cpp +++ b/indra/llcommon/llsdserialize_xml.cpp @@ -31,6 +31,8 @@ #include <deque> #include "apr_base64.h" +#include <boost/iostreams/device/array.hpp> +#include <boost/iostreams/stream.hpp> #include <boost/regex.hpp> extern "C" @@ -645,7 +647,7 @@ void LLSDXMLParser::Impl::startElementHandler(const XML_Char* name, const XML_Ch if (mCurrentKey.empty()) { return startSkipping(); } LLSD& map = *mStack.back(); - LLSD& newElement = map[mCurrentKey]; + LLSD& newElement = map[std::move(mCurrentKey)]; mStack.push_back(&newElement); mCurrentKey.clear(); @@ -709,7 +711,8 @@ void LLSDXMLParser::Impl::endElementHandler(const XML_Char* name) return; case ELEMENT_KEY: - mCurrentKey = mCurrentContent; + mCurrentKey = std::move(mCurrentContent); // This is safe to move as we are in the end element handler + mCurrentContent.clear(); // Clear to reset to valid state return; default: @@ -742,14 +745,39 @@ void LLSDXMLParser::Impl::endElementHandler(const XML_Char* name) } else { - value = LLSD(mCurrentContent).asInteger(); + // This implementation is copied from llsd.cpp + F64 v = 0.0; + boost::iostreams::stream<boost::iostreams::array_source> i_stream(mCurrentContent.data(), mCurrentContent.size()); + i_stream >> v; + + // we would probably like to ignore all trailing whitespace as + // well, but for now, simply eat the next character, and make + // sure we reached the end of the string. + // *NOTE: gcc 2.95 does not generate an eof() event on the + // stream operation above, so we manually get here to force it + // across platforms. + int c = i_stream.get(); + value = (int)((EOF == c) ? v : 0.0); } } break; case ELEMENT_REAL: { - value = LLSD(mCurrentContent).asReal(); + // This implementation is copied from llsd.cpp + F64 v = 0.0; + boost::iostreams::stream<boost::iostreams::array_source> i_stream(mCurrentContent.data(), mCurrentContent.size()); + i_stream >> v; + + // we would probably like to ignore all trailing whitespace as + // well, but for now, simply eat the next character, and make + // sure we reached the end of the string. + // *NOTE: gcc 2.95 does not generate an eof() event on the + // stream operation above, so we manually get here to force it + // across platforms. + int c = i_stream.get(); + value = ((EOF == c) ? v : 0.0); + // removed since this breaks when locale has decimal separator that isn't '.' // investigated changing local to something compatible each time but deemed higher // risk that just using LLSD.asReal() each time. @@ -766,19 +794,19 @@ void LLSDXMLParser::Impl::endElementHandler(const XML_Char* name) break; case ELEMENT_STRING: - value = mCurrentContent; + value = std::move(mCurrentContent); // This is safe to move as we are in the end element handler and this is cleared below break; case ELEMENT_UUID: - value = LLSD(mCurrentContent).asUUID(); + value = LLUUID(mCurrentContent); break; case ELEMENT_DATE: - value = LLSD(mCurrentContent).asDate(); + value = LLDate(mCurrentContent); break; case ELEMENT_URI: - value = LLSD(mCurrentContent).asURI(); + value = LLURI(mCurrentContent); break; case ELEMENT_BINARY: @@ -787,15 +815,14 @@ void LLSDXMLParser::Impl::endElementHandler(const XML_Char* name) // created by python and other non-linden systems - DEV-39358 // Fortunately we have very little binary passing now, // so performance impact shold be negligible. + poppy 2009-09-04 - boost::regex r; - r.assign("\\s"); + static const boost::regex r("\\s"); std::string stripped = boost::regex_replace(mCurrentContent, r, ""); S32 len = apr_base64_decode_len(stripped.c_str()); std::vector<U8> data; data.resize(len); len = apr_base64_decode_binary(&data[0], stripped.c_str()); data.resize(len); - value = data; + value = std::move(data); break; } |
