Skip to content

Commit 7e359c2

Browse files
committed
Optimisation
Signed-off-by: JCW <[email protected]>
1 parent 3c6fe58 commit 7e359c2

File tree

3 files changed

+153
-29
lines changed

3 files changed

+153
-29
lines changed

include/xrpl/beast/utility/Journal.h

Lines changed: 117 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -82,73 +82,77 @@ class SimpleJsonWriter
8282
public:
8383
explicit SimpleJsonWriter(std::ostringstream& stream) : stream_(stream)
8484
{
85+
stream_.imbue(std::locale::classic());
8586
}
8687

8788
void
8889
startObject() const
8990
{
90-
stream_ << "{";
91+
stream_.put('{');
9192
}
9293
void
9394
endObject() const
9495
{
9596
stream_.seekp(-1, std::ios_base::end);
96-
stream_ << "},";
97+
stream_.write("},", 2);
9798
}
9899
void
99100
writeKey(std::string_view key) const
100101
{
101102
writeString(key);
102103
stream_.seekp(-1, std::ios_base::end);
103-
stream_ << ":";
104+
stream_.put(':');
104105
}
105106
void
106107
startArray() const
107108
{
108-
stream_ << "[";
109+
stream_.put('[');
109110
}
110111
void
111112
endArray() const
112113
{
113114
stream_.seekp(-1, std::ios_base::end);
114-
stream_ << "],";
115+
stream_.write("],", 2);
115116
}
116117
void
117118
writeString(std::string_view str) const
118119
{
119-
stream_ << "\"";
120+
stream_.put('"');
120121
escape(str, stream_);
121-
stream_ << "\",";
122+
stream_.write("\",", 2);
122123
}
123-
void
124+
std::string_view
124125
writeInt(std::int64_t val) const
125126
{
126-
stream_ << val << ",";
127+
return pushNumber(val, stream_);
127128
}
128-
void
129+
std::string_view
129130
writeUInt(std::uint64_t val) const
130131
{
131-
stream_ << val << ",";
132+
return pushNumber(val, stream_);
132133
}
133-
void
134+
std::string_view
134135
writeDouble(double val) const
135136
{
136-
stream_ << val << ",";
137+
return pushNumber(val, stream_);
137138
}
138-
void
139+
std::string_view
139140
writeBool(bool val) const
140141
{
141-
stream_ << (val ? "true," : "false,");
142+
auto str = val ? "true," : "false,";
143+
stream_.write(str, std::strlen(str));
144+
return str;
142145
}
143146
void
144147
writeNull() const
145148
{
146-
stream_ << "null" << ",";
149+
stream_.write("null", std::strlen("null"));
150+
stream_.put(',');
147151
}
148152
void
149153
writeRaw(std::string_view str) const
150154
{
151-
stream_ << str;
155+
stream_.write(str.data(), str.length());
152156
}
153157

154158
[[nodiscard]] std::string
@@ -160,11 +164,69 @@ class SimpleJsonWriter
160164
}
161165

162166
private:
167+
template <typename T>
168+
static std::string_view
169+
pushNumber(T val, std::ostringstream& stream)
170+
{
171+
static char buffer[128];
172+
auto result = std::to_chars(std::begin(buffer), std::end(buffer), val);
173+
*result.ptr = ',';
174+
auto len = result.ptr - std::begin(buffer);
175+
stream.write(buffer, len + 1);
176+
return {buffer, static_cast<size_t>(len)};
177+
}
178+
163179
static void
164180
escape(std::string_view str, std::ostringstream& os)
165181
{
166-
// TODO: Support it
167-
os << str;
182+
static constexpr char HEX[] = "0123456789ABCDEF";
183+
184+
const char* p = str.data();
185+
const char* end = p + str.size();
186+
const char* chunk = p;
187+
188+
while (p < end) {
189+
auto c = static_cast<unsigned char>(*p);
190+
191+
// JSON requires escaping for <0x20 and the two specials below.
192+
bool needsEscape = (c < 0x20) || (c == '"') || (c == '\\');
193+
194+
if (!needsEscape) {
195+
++p;
196+
continue;
197+
}
198+
199+
// Flush the preceding safe run in one go.
200+
if (chunk != p)
201+
os.write(chunk, p - chunk);
202+
203+
switch (c) {
204+
case '"': os.write("\\\"", 2); break;
205+
case '\\': os.write("\\\\", 2); break;
206+
case '\b': os.write("\\b", 2); break;
207+
case '\f': os.write("\\f", 2); break;
208+
case '\n': os.write("\\n", 2); break;
209+
case '\r': os.write("\\r", 2); break;
210+
case '\t': os.write("\\t", 2); break;
211+
default: {
212+
// Other C0 controls -> \u00XX (JSON compliant)
213+
char buf[6]{
214+
'\\','u','0','0',
215+
HEX[(c >> 4) & 0xF],
216+
HEX[c & 0xF]
217+
};
218+
os.write(buf, 6);
219+
break;
220+
}
221+
}
222+
223+
++p;
224+
chunk = p;
225+
}
226+
227+
// Flush trailing safe run
228+
if (chunk != p)
229+
os.write(chunk, p - chunk);
168230
}
169231

170232
std::ostringstream& stream_;
@@ -769,6 +831,13 @@ using logwstream = basic_logstream<wchar_t>;
769831
namespace ripple::log {
770832

771833
namespace detail {
834+
835+
template <typename T>
836+
concept CanToChars = requires (T val)
837+
{
838+
{ to_chars(std::declval<char*>(), std::declval<char*>(), val) } -> std::convertible_to<std::to_chars_result>;
839+
};
840+
772841
template <typename T>
773842
void
774843
setJsonValue(
@@ -781,34 +850,35 @@ setJsonValue(
781850
writer.writeKey(name);
782851
if constexpr (std::is_integral_v<ValueType>)
783852
{
853+
std::string_view sv;
784854
if constexpr (std::is_signed_v<ValueType>)
785855
{
786-
writer.writeInt(value);
856+
sv = writer.writeInt(value);
787857
}
788858
else
789859
{
790-
writer.writeUInt(value);
860+
sv = writer.writeUInt(value);
791861
}
792862
if (outStream)
793863
{
794-
(*outStream) << value;
864+
outStream->write(sv.data(), sv.size());
795865
}
796866
}
797867
else if constexpr (std::is_floating_point_v<ValueType>)
798868
{
799-
writer.writeDouble(value);
869+
auto sv = writer.writeDouble(value);
800870

801871
if (outStream)
802872
{
803-
(*outStream) << value;
873+
outStream->write(sv.data(), sv.size());
804874
}
805875
}
806876
else if constexpr (std::is_same_v<ValueType, bool>)
807877
{
808-
writer.writeBool(value);
878+
auto sv = writer.writeBool(value);
809879
if (outStream)
810880
{
811-
(*outStream) << value;
881+
outStream->write(sv.data(), sv.size());
812882
}
813883
}
814884
else if constexpr (
@@ -818,20 +888,38 @@ setJsonValue(
818888
writer.writeString(value);
819889
if (outStream)
820890
{
821-
(*outStream) << value;
891+
outStream->write(value, std::strlen(value));
822892
}
823893
}
824894
else if constexpr (std::is_same_v<ValueType, std::string>)
825895
{
826896
writer.writeString(value);
827897
if (outStream)
828898
{
829-
(*outStream) << value;
899+
outStream->write(value.c_str(), value.length());
830900
}
831901
}
832902
else
833903
{
904+
if constexpr (CanToChars<ValueType>)
905+
{
906+
char buffer[1024];
907+
std::to_chars_result result = to_chars(std::begin(buffer), std::end(buffer), value);
908+
if (result.ec == std::errc{})
909+
{
910+
std::string_view sv;
911+
sv = {std::begin(buffer), result.ptr};
912+
writer.writeString(sv);
913+
if (outStream)
914+
{
915+
outStream->write(sv.data(), sv.size());
916+
}
917+
return;
918+
}
919+
}
920+
834921
std::ostringstream oss;
922+
oss.imbue(std::locale::classic());
835923
oss << value;
836924

837925
auto str = oss.str();
@@ -840,7 +928,7 @@ setJsonValue(
840928

841929
if (outStream)
842930
{
843-
(*outStream) << str;
931+
outStream->write(str.c_str(), static_cast<std::streamsize>(str.size()));
844932
}
845933
}
846934
}

src/libxrpl/beast/utility/beast_Journal.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ Journal::JsonLogContext::reset(
131131
ThreadIdStringInitializer()
132132
{
133133
std::stringstream threadIdStream;
134+
threadIdStream.imbue(std::locale::classic());
134135
threadIdStream << std::this_thread::get_id();
135136
value = threadIdStream.str();
136137
}

src/tests/libxrpl/basics/log.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,41 @@ TEST_CASE("Global attributes inheritable")
209209
beast::Journal::disableStructuredJournal();
210210
}
211211

212+
TEST_CASE("Test JsonWriter")
213+
{
214+
{
215+
std::ostringstream stream;
216+
beast::SimpleJsonWriter writer{stream};
217+
218+
writer.writeString("\n");
219+
CHECK(writer.str() == "\"\\n\"");
220+
}
221+
222+
{
223+
std::ostringstream stream;
224+
beast::SimpleJsonWriter writer{stream};
225+
226+
writer.writeString("\t");
227+
CHECK(writer.str() == "\"\\t\"");
228+
}
229+
230+
{
231+
std::ostringstream stream;
232+
beast::SimpleJsonWriter writer{stream};
233+
234+
writer.writeString(std::string_view{"\0", 1});
235+
CHECK(writer.str() == "\"\\u0000\"");
236+
}
237+
238+
{
239+
std::ostringstream stream;
240+
beast::SimpleJsonWriter writer{stream};
241+
242+
writer.writeString("\"\\");
243+
CHECK(writer.str() == "\"\\\"\\\\\"");
244+
}
245+
}
246+
212247
/**
213248
* @brief sink for writing all log messages to a stringstream
214249
*/

0 commit comments

Comments
 (0)