Skip to content
This repository has been archived by the owner on Feb 15, 2024. It is now read-only.

Commit

Permalink
univalue optimizations: move semantics, reduce temporary strings
Browse files Browse the repository at this point in the history
* Introducing move semantics for pushKV and push_back. In many use cases
  the const& calls unnecessarily create a copy of the argument, when
  objects can be moved this does not happen.

* Reduce creation of temporary strings. `json_escape` and `write` now
  appends to an existing string instead of creating a temporary.

* Use `std::to_string` instead of `std::ostringstream` for `setNumStr`,
  which is much faster than creating a temporary string.

In a benchmark with Bitcoin's BlockToJsonVerbose, using these move
methods where possible speeds up JSON generation by about a factor of 2.
  • Loading branch information
martinus committed Jan 25, 2021
1 parent d6715ee commit e9109e2
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 33 deletions.
21 changes: 19 additions & 2 deletions include/univalue.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class UniValue {

UniValue() : typ(VNULL) {}
UniValue(UniValue::VType type, const std::string& value = std::string()) : typ(type), val(value) {}
UniValue(UniValue::VType type, std::string&& value) : typ(type), val(std::move(value)) {}
UniValue(uint64_t val_) {
setInt(val_);
}
Expand All @@ -38,9 +39,11 @@ class UniValue {
UniValue(const std::string& val_) {
setStr(val_);
}
UniValue(std::string&& val_) {
setStr(std::move(val_));
}
UniValue(const char *val_) {
std::string s(val_);
setStr(s);
setStr(std::string(val_));
}

void clear();
Expand All @@ -57,11 +60,13 @@ class UniValue {
bool setNull();
bool setBool(bool val);
bool setNumStr(const std::string& val);
bool setNumStr(std::string&& val);
bool setInt(uint64_t val);
bool setInt(int64_t val);
bool setInt(int val_) { return setInt((int64_t)val_); }
bool setFloat(double val);
bool setStr(const std::string& val);
bool setStr(std::string&& val);
bool setArray();
bool setObject();

Expand All @@ -88,11 +93,22 @@ class UniValue {
bool isObject() const { return (typ == VOBJ); }

bool push_back(const UniValue& val);
bool push_back(UniValue&& val);
bool push_backV(const std::vector<UniValue>& vec);
bool push_backV(std::vector<UniValue>&& vec);

void __pushKV(const std::string& key, const UniValue& val);
void __pushKV(const std::string& key, UniValue&& val);
void __pushKV(std::string&& key, const UniValue& val);
void __pushKV(std::string&& key, UniValue&& val);

bool pushKV(const std::string& key, const UniValue& val);
bool pushKV(const std::string& key, UniValue&& val);
bool pushKV(std::string&& key, const UniValue& val);
bool pushKV(std::string&& key, UniValue&& val);

bool pushKVs(const UniValue& obj);
bool pushKVs(UniValue&& obj);

std::string write(unsigned int prettyIndent = 0,
unsigned int indentLevel = 0) const;
Expand All @@ -110,6 +126,7 @@ class UniValue {
std::vector<UniValue> values;

bool findKey(const std::string& key, size_t& retIdx) const;
void write(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const;
void writeArray(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const;
void writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const;

Expand Down
116 changes: 107 additions & 9 deletions lib/univalue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,25 @@ bool UniValue::setNumStr(const std::string& val_)
return true;
}

bool UniValue::setInt(uint64_t val_)
bool UniValue::setNumStr(std::string&& val_)
{
std::ostringstream oss;
if (!validNumStr(val_))
return false;

oss << val_;
clear();
typ = VNUM;
val = std::move(val_);
return true;
}

return setNumStr(oss.str());
bool UniValue::setInt(uint64_t val_)
{
return setNumStr(std::to_string(val_));
}

bool UniValue::setInt(int64_t val_)
{
std::ostringstream oss;

oss << val_;

return setNumStr(oss.str());
return setNumStr(std::to_string(val_));
}

bool UniValue::setFloat(double val_)
Expand All @@ -91,6 +94,14 @@ bool UniValue::setStr(const std::string& val_)
return true;
}

bool UniValue::setStr(std::string&& val_)
{
clear();
typ = VSTR;
val = std::move(val_);
return true;
}

bool UniValue::setArray()
{
clear();
Expand All @@ -114,6 +125,15 @@ bool UniValue::push_back(const UniValue& val_)
return true;
}

bool UniValue::push_back(UniValue&& val_)
{
if (typ != VARR)
return false;

values.push_back(std::move(val_));
return true;
}

bool UniValue::push_backV(const std::vector<UniValue>& vec)
{
if (typ != VARR)
Expand All @@ -124,12 +144,40 @@ bool UniValue::push_backV(const std::vector<UniValue>& vec)
return true;
}

bool UniValue::push_backV(std::vector<UniValue>&& vec)
{
if (typ != VARR)
return false;

values.insert(values.end(), std::make_move_iterator(vec.begin()), std::make_move_iterator(vec.end()));

return true;
}

void UniValue::__pushKV(const std::string& key, const UniValue& val_)
{
keys.push_back(key);
values.push_back(val_);
}

void UniValue::__pushKV(const std::string& key, UniValue&& val_)
{
keys.push_back(key);
values.push_back(std::move(val_));
}

void UniValue::__pushKV(std::string&& key, const UniValue& val_)
{
keys.push_back(std::move(key));
values.push_back(val_);
}

void UniValue::__pushKV(std::string&& key, UniValue&& val_)
{
keys.push_back(std::move(key));
values.push_back(std::move(val_));
}

bool UniValue::pushKV(const std::string& key, const UniValue& val_)
{
if (typ != VOBJ)
Expand All @@ -143,6 +191,45 @@ bool UniValue::pushKV(const std::string& key, const UniValue& val_)
return true;
}

bool UniValue::pushKV(const std::string& key, UniValue&& val_)
{
if (typ != VOBJ)
return false;

size_t idx;
if (findKey(key, idx))
values[idx] = std::move(val_);
else
__pushKV(key, std::move(val_));
return true;
}

bool UniValue::pushKV(std::string&& key, const UniValue& val_)
{
if (typ != VOBJ)
return false;

size_t idx;
if (findKey(key, idx))
values[idx] = val_;
else
__pushKV(std::move(key), val_);
return true;
}

bool UniValue::pushKV(std::string&& key, UniValue&& val_)
{
if (typ != VOBJ)
return false;

size_t idx;
if (findKey(key, idx))
values[idx] = std::move(val_);
else
__pushKV(std::move(key), std::move(val_));
return true;
}

bool UniValue::pushKVs(const UniValue& obj)
{
if (typ != VOBJ || obj.typ != VOBJ)
Expand All @@ -154,6 +241,17 @@ bool UniValue::pushKVs(const UniValue& obj)
return true;
}

bool UniValue::pushKVs(UniValue&& obj)
{
if (typ != VOBJ || obj.typ != VOBJ)
return false;

for (size_t i = 0; i < obj.keys.size(); i++)
__pushKV(std::move(obj.keys[i]), std::move(obj.values.at(i)));

return true;
}

void UniValue::getObjMap(std::map<std::string,UniValue>& kv) const
{
if (typ != VOBJ)
Expand Down
18 changes: 9 additions & 9 deletions lib/univalue_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ enum jtokentype getJsonToken(std::string& tokenVal, unsigned int& consumed,
}
}

tokenVal = numStr;
tokenVal = std::move(numStr);
consumed = (raw - rawStart);
return JTOK_NUMBER;
}
Expand Down Expand Up @@ -234,7 +234,7 @@ enum jtokentype getJsonToken(std::string& tokenVal, unsigned int& consumed,

if (!writer.finalize())
return JTOK_ERR;
tokenVal = valStr;
tokenVal = std::move(valStr);
consumed = (raw - rawStart);
return JTOK_STRING;
}
Expand Down Expand Up @@ -325,7 +325,7 @@ bool UniValue::read(const char *raw, size_t size)
} else {
UniValue tmpVal(utyp);
UniValue *top = stack.back();
top->values.push_back(tmpVal);
top->values.push_back(std::move(tmpVal));

UniValue *newTop = &(top->values.back());
stack.push_back(newTop);
Expand Down Expand Up @@ -400,12 +400,12 @@ bool UniValue::read(const char *raw, size_t size)
}

if (!stack.size()) {
*this = tmpVal;
*this = std::move(tmpVal);
break;
}

UniValue *top = stack.back();
top->values.push_back(tmpVal);
top->values.push_back(std::move(tmpVal));

setExpect(NOT_VALUE);
break;
Expand All @@ -414,12 +414,12 @@ bool UniValue::read(const char *raw, size_t size)
case JTOK_NUMBER: {
UniValue tmpVal(VNUM, tokenVal);
if (!stack.size()) {
*this = tmpVal;
*this = std::move(tmpVal);
break;
}

UniValue *top = stack.back();
top->values.push_back(tmpVal);
top->values.push_back(std::move(tmpVal));

setExpect(NOT_VALUE);
break;
Expand All @@ -434,11 +434,11 @@ bool UniValue::read(const char *raw, size_t size)
} else {
UniValue tmpVal(VSTR, tokenVal);
if (!stack.size()) {
*this = tmpVal;
*this = std::move(tmpVal);
break;
}
UniValue *top = stack.back();
top->values.push_back(tmpVal);
top->values.push_back(std::move(tmpVal));
}

setExpect(NOT_VALUE);
Expand Down
29 changes: 16 additions & 13 deletions lib/univalue_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@
#include "univalue.h"
#include "univalue_escapes.h"

static std::string json_escape(const std::string& inS)
static void json_escape(const std::string& inS, std::string& outS)
{
std::string outS;
outS.reserve(inS.size() * 2);

for (unsigned int i = 0; i < inS.size(); i++) {
unsigned char ch = inS[i];
const char *escStr = escapes[ch];
Expand All @@ -21,16 +18,20 @@ static std::string json_escape(const std::string& inS)
else
outS += ch;
}

return outS;
}

std::string UniValue::write(unsigned int prettyIndent,
unsigned int indentLevel) const
{
std::string s;
s.reserve(1024);
write(prettyIndent, indentLevel, s);
return s;
}

void UniValue::write(unsigned int prettyIndent,
unsigned int indentLevel,
std::string& s) const
{
unsigned int modIndent = indentLevel;
if (modIndent == 0)
modIndent = 1;
Expand All @@ -46,7 +47,9 @@ std::string UniValue::write(unsigned int prettyIndent,
writeArray(prettyIndent, modIndent, s);
break;
case VSTR:
s += "\"" + json_escape(val) + "\"";
s += "\"";
json_escape(val, s);
s += "\"";
break;
case VNUM:
s += val;
Expand All @@ -55,8 +58,6 @@ std::string UniValue::write(unsigned int prettyIndent,
s += (val == "1" ? "true" : "false");
break;
}

return s;
}

static void indentStr(unsigned int prettyIndent, unsigned int indentLevel, std::string& s)
Expand All @@ -73,7 +74,7 @@ void UniValue::writeArray(unsigned int prettyIndent, unsigned int indentLevel, s
for (unsigned int i = 0; i < values.size(); i++) {
if (prettyIndent)
indentStr(prettyIndent, indentLevel, s);
s += values[i].write(prettyIndent, indentLevel + 1);
values[i].write(prettyIndent, indentLevel + 1, s);
if (i != (values.size() - 1)) {
s += ",";
}
Expand All @@ -95,10 +96,12 @@ void UniValue::writeObject(unsigned int prettyIndent, unsigned int indentLevel,
for (unsigned int i = 0; i < keys.size(); i++) {
if (prettyIndent)
indentStr(prettyIndent, indentLevel, s);
s += "\"" + json_escape(keys[i]) + "\":";
s += "\"";
json_escape(keys[i], s);
s += "\":";
if (prettyIndent)
s += " ";
s += values.at(i).write(prettyIndent, indentLevel + 1);
values.at(i).write(prettyIndent, indentLevel + 1, s);
if (i != (values.size() - 1))
s += ",";
if (prettyIndent)
Expand Down

0 comments on commit e9109e2

Please sign in to comment.