Skip to content
This repository was archived by the owner on Jun 17, 2022. It is now read-only.

Commit 10718e3

Browse files
committed
Introduce move semantics, with helper to convert legacy code
Introduces move semantics when creating UniValue or adding data with push_back or pushKV. In many cases using these methods can dramatically reduce the amount of useless work done. To get the most benefit of these changes it is necessary to walk through the code and update it by adding e.g. std::move where appropriate. To make this task easier it is possible to define the macro NO_UNIVALUE_COPY_OPERATIONS which disables all UniValue methods that produce a copy. Note that the copy constructor still needs to be there for use in e.g. std::vector, but it is now marked as explicit. With NO_UNIVALUE_COPY_OPERATIONS defined, the compiler will produce an error in each case where a copy would have occurred. To fix these compile errors, ideally use std::move. When a copy is still needed, the compile error can be silenced by explicitly calling the copy() method of UniValue. In my experiment, when adding #define NO_UNIVALUE_COPY_OPERATIONS to Bitcoin's core_write.cpp and blockchain.cpp, the compile errors can be fixed with 29 std::move(), 9 copy(), and 1 const&. This almost doubles the performance of the BlockToJsonVerbose benchmark.
1 parent 90bf98e commit 10718e3

File tree

4 files changed

+328
-93
lines changed

4 files changed

+328
-93
lines changed

include/univalue.h

Lines changed: 92 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@
66
#ifndef __UNIVALUE_H__
77
#define __UNIVALUE_H__
88

9+
// Legacy code that uses UniValue typically makes countless copies of UniValue
10+
// objects. These copies are often unnecessary and costly. To help with
11+
// converting legacy code into more efficient std::move friendly code, enable
12+
// NO_UNIVALUE_COPY_OPERATIONS. This will disable all UniValue methods that
13+
// cause copying of elements, causing compile errors. Each of these compile
14+
// errors can then be fixed with e.g. std::move(), or if a copy is still
15+
// necessary by explicitly calling the .copy() method. Unfortunately it cannot
16+
// detect all cases, as a copy constructor is still necessary for collections.
17+
//
18+
// You can disable copy operations here globally, or in individual files by
19+
// setting it before the first univalue.h include.
20+
//
21+
//#define NO_UNIVALUE_COPY_OPERATIONS
22+
923
#include <stdint.h>
1024
#include <string.h>
1125

@@ -18,11 +32,32 @@ class UniValue {
1832
public:
1933
enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, };
2034

21-
UniValue() { typ = VNULL; }
22-
UniValue(UniValue::VType initialType, const std::string& initialStr = "") {
23-
typ = initialType;
24-
val = initialStr;
35+
UniValue() = default;
36+
37+
#if defined(NO_UNIVALUE_COPY_OPERATIONS)
38+
// explicit copy constructor and deleted copy assignment forces the use of .copy() in most cases.
39+
explicit UniValue(const UniValue&) = default;
40+
UniValue& operator=(const UniValue&) = delete;
41+
#else
42+
UniValue(const UniValue&) = default;
43+
UniValue& operator=(const UniValue&) = default;
44+
#endif
45+
46+
UniValue(UniValue&&) = default;
47+
UniValue& operator=(UniValue&&) = default;
48+
~UniValue() = default;
49+
50+
// adds an explicit copy() operation that also works even when NO_UNIVALUE_COPY_OPERATIONS is defined.
51+
UniValue copy() const {
52+
return UniValue{*this};
2553
}
54+
55+
UniValue(UniValue::VType initialType) : typ(initialType) {}
56+
UniValue(UniValue::VType initialType, const std::string& initialStr) : typ(initialType), val(initialStr) {}
57+
UniValue(UniValue::VType initialType, std::string&& initialStr) : typ(initialType), val(std::move(initialStr)) {}
58+
UniValue(const std::string& val_) : typ(VSTR), val(val_) {}
59+
UniValue(std::string&& val_) : typ(VSTR), val(std::move(val_)) {}
60+
UniValue(const char *val_) : typ(VSTR), val(val_) {}
2661
UniValue(uint64_t val_) {
2762
setInt(val_);
2863
}
@@ -38,24 +73,19 @@ class UniValue {
3873
UniValue(double val_) {
3974
setFloat(val_);
4075
}
41-
UniValue(const std::string& val_) {
42-
setStr(val_);
43-
}
44-
UniValue(const char *val_) {
45-
std::string s(val_);
46-
setStr(s);
47-
}
4876

4977
void clear();
5078

5179
bool setNull();
5280
bool setBool(bool val);
5381
bool setNumStr(const std::string& val);
82+
bool setNumStr(std::string&& val);
5483
bool setInt(uint64_t val);
5584
bool setInt(int64_t val);
5685
bool setInt(int val_) { return setInt((int64_t)val_); }
5786
bool setFloat(double val);
5887
bool setStr(const std::string& val);
88+
bool setStr(std::string&& val);
5989
bool setArray();
6090
bool setObject();
6191

@@ -81,68 +111,56 @@ class UniValue {
81111
bool isArray() const { return (typ == VARR); }
82112
bool isObject() const { return (typ == VOBJ); }
83113

114+
#if !defined(NO_UNIVALUE_COPY_OPERATIONS)
84115
bool push_back(const UniValue& val);
85-
bool push_back(const std::string& val_) {
86-
UniValue tmpVal(VSTR, val_);
87-
return push_back(tmpVal);
88-
}
89-
bool push_back(const char *val_) {
90-
std::string s(val_);
91-
return push_back(s);
92-
}
93-
bool push_back(uint64_t val_) {
94-
UniValue tmpVal(val_);
95-
return push_back(tmpVal);
96-
}
97-
bool push_back(int64_t val_) {
98-
UniValue tmpVal(val_);
99-
return push_back(tmpVal);
100-
}
101-
bool push_back(bool val_) {
102-
UniValue tmpVal(val_);
103-
return push_back(tmpVal);
104-
}
105-
bool push_back(int val_) {
106-
UniValue tmpVal(val_);
107-
return push_back(tmpVal);
108-
}
109-
bool push_back(double val_) {
110-
UniValue tmpVal(val_);
111-
return push_back(tmpVal);
112-
}
116+
#endif
117+
bool push_back(UniValue&& val);
118+
bool push_back(const std::string& val_);
119+
bool push_back(std::string&& val_);
120+
bool push_back(const char *val_);
121+
bool push_back(uint64_t val_);
122+
bool push_back(int64_t val_);
123+
bool push_back(bool val_);
124+
bool push_back(int val_);
125+
bool push_back(double val_);
126+
#if !defined(NO_UNIVALUE_COPY_OPERATIONS)
113127
bool push_backV(const std::vector<UniValue>& vec);
128+
#endif
129+
bool push_backV(std::vector<UniValue>&& vec);
114130

131+
#if !defined(NO_UNIVALUE_COPY_OPERATIONS)
115132
void __pushKV(const std::string& key, const UniValue& val);
133+
void __pushKV(std::string&& key, const UniValue& val);
134+
#endif
135+
void __pushKV(const std::string& key, UniValue&& val);
136+
void __pushKV(std::string&& key, UniValue&& val);
137+
138+
#if !defined(NO_UNIVALUE_COPY_OPERATIONS)
116139
bool pushKV(const std::string& key, const UniValue& val);
117-
bool pushKV(const std::string& key, const std::string& val_) {
118-
UniValue tmpVal(VSTR, val_);
119-
return pushKV(key, tmpVal);
120-
}
121-
bool pushKV(const std::string& key, const char *val_) {
122-
std::string _val(val_);
123-
return pushKV(key, _val);
124-
}
125-
bool pushKV(const std::string& key, int64_t val_) {
126-
UniValue tmpVal(val_);
127-
return pushKV(key, tmpVal);
128-
}
129-
bool pushKV(const std::string& key, uint64_t val_) {
130-
UniValue tmpVal(val_);
131-
return pushKV(key, tmpVal);
132-
}
133-
bool pushKV(const std::string& key, bool val_) {
134-
UniValue tmpVal(val_);
135-
return pushKV(key, tmpVal);
136-
}
137-
bool pushKV(const std::string& key, int val_) {
138-
UniValue tmpVal((int64_t)val_);
139-
return pushKV(key, tmpVal);
140-
}
141-
bool pushKV(const std::string& key, double val_) {
142-
UniValue tmpVal(val_);
143-
return pushKV(key, tmpVal);
144-
}
140+
bool pushKV(std::string&& key, const UniValue& val);
141+
#endif
142+
bool pushKV(const std::string& key, UniValue&& val);
143+
bool pushKV(std::string&& key, UniValue&& val);
144+
bool pushKV(const std::string& key, const std::string& val);
145+
bool pushKV(std::string&& key, const std::string& val);
146+
bool pushKV(const std::string& key, std::string&& val);
147+
bool pushKV(std::string&& key, std::string&& val);
148+
bool pushKV(const std::string& key, const char *val);
149+
bool pushKV(std::string&& key, const char *val);
150+
bool pushKV(const std::string& key, int64_t val);
151+
bool pushKV(std::string&& key, int64_t val);
152+
bool pushKV(const std::string& key, uint64_t val);
153+
bool pushKV(std::string&& key, uint64_t val);
154+
bool pushKV(const std::string& key, bool val);
155+
bool pushKV(std::string&& key, bool val);
156+
bool pushKV(const std::string& key, int val);
157+
bool pushKV(std::string&& key, int val);
158+
bool pushKV(const std::string& key, double val);
159+
bool pushKV(std::string&& key, double val);
160+
#if !defined(NO_UNIVALUE_COPY_OPERATIONS)
145161
bool pushKVs(const UniValue& obj);
162+
#endif
163+
bool pushKVs(UniValue&& obj);
146164

147165
std::string write(unsigned int prettyIndent = 0,
148166
unsigned int indentLevel = 0) const;
@@ -154,7 +172,13 @@ class UniValue {
154172
}
155173

156174
private:
157-
UniValue::VType typ;
175+
template<typename V>
176+
bool pushBackGeneric(V&& val_);
177+
178+
template <typename K, typename V>
179+
bool pushKVGeneric(K&& key, V&& val_);
180+
181+
UniValue::VType typ{VNULL};
158182
std::string val; // numbers are stored as C++ strings
159183
std::vector<std::string> keys;
160184
std::vector<UniValue> values;

0 commit comments

Comments
 (0)