26a8b220eb
we don't have to create an ostream sentry object for every character of a JSON string we write. format a bunch of characters and flush them to the stream all at once instead. this doesn't affect small numbers of string characters, but larger numbers of total JSON string characters written gain a lot. at 1MB of total string written we gain almost 30%, at 16MB it's almost a factor of 3x. large numbers of JSON string characters do occur naturally in a nixos system evaluation to generate documentation (though this is now somewhat mitigated by caching the largest part of nixos option docs). benchmarked with hyperfine 'nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) {e})"' --warmup 1 -L e 1,4,256,4096,65536 before: Benchmark 1: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 1)" Time (mean ± σ): 12.5 ms ± 0.2 ms [User: 9.2 ms, System: 4.0 ms] Range (min … max): 11.9 ms … 13.1 ms 223 runs Benchmark 2: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 4)" Time (mean ± σ): 12.5 ms ± 0.2 ms [User: 9.3 ms, System: 3.8 ms] Range (min … max): 11.9 ms … 13.2 ms 220 runs Benchmark 3: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 256)" Time (mean ± σ): 13.2 ms ± 0.3 ms [User: 9.8 ms, System: 4.0 ms] Range (min … max): 12.6 ms … 14.3 ms 205 runs Benchmark 4: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 4096)" Time (mean ± σ): 24.0 ms ± 0.4 ms [User: 19.4 ms, System: 5.2 ms] Range (min … max): 22.7 ms … 25.8 ms 119 runs Benchmark 5: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 65536)" Time (mean ± σ): 196.0 ms ± 3.7 ms [User: 171.2 ms, System: 25.8 ms] Range (min … max): 190.6 ms … 201.5 ms 14 runs after: Benchmark 1: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 1)" Time (mean ± σ): 12.4 ms ± 0.3 ms [User: 9.1 ms, System: 4.0 ms] Range (min … max): 11.7 ms … 13.3 ms 204 runs Benchmark 2: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 4)" Time (mean ± σ): 12.4 ms ± 0.2 ms [User: 9.2 ms, System: 3.9 ms] Range (min … max): 11.8 ms … 13.0 ms 214 runs Benchmark 3: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 256)" Time (mean ± σ): 12.6 ms ± 0.2 ms [User: 9.5 ms, System: 3.8 ms] Range (min … max): 12.1 ms … 13.3 ms 209 runs Benchmark 4: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 4096)" Time (mean ± σ): 15.9 ms ± 0.2 ms [User: 11.4 ms, System: 5.1 ms] Range (min … max): 15.2 ms … 16.4 ms 171 runs Benchmark 5: nix eval --raw --expr "let s = __concatStringsSep \"\" (__genList (_: \"c\") 256); in __toJSON (__genList (_: s) 65536)" Time (mean ± σ): 69.0 ms ± 0.9 ms [User: 44.3 ms, System: 25.3 ms] Range (min … max): 67.2 ms … 70.9 ms 42 runs
202 lines
4.3 KiB
C++
202 lines
4.3 KiB
C++
#include "json.hh"
|
|
|
|
#include <iomanip>
|
|
#include <cstring>
|
|
|
|
namespace nix {
|
|
|
|
void toJSON(std::ostream & str, const char * start, const char * end)
|
|
{
|
|
constexpr size_t BUF_SIZE = 4096;
|
|
char buf[BUF_SIZE + 7]; // BUF_SIZE + largest single sequence of puts
|
|
size_t bufPos = 0;
|
|
|
|
const auto flush = [&] {
|
|
str.write(buf, bufPos);
|
|
bufPos = 0;
|
|
};
|
|
const auto put = [&] (char c) {
|
|
buf[bufPos++] = c;
|
|
};
|
|
|
|
put('"');
|
|
for (auto i = start; i != end; i++) {
|
|
if (bufPos >= BUF_SIZE) flush();
|
|
if (*i == '\"' || *i == '\\') { put('\\'); put(*i); }
|
|
else if (*i == '\n') { put('\\'); put('n'); }
|
|
else if (*i == '\r') { put('\\'); put('r'); }
|
|
else if (*i == '\t') { put('\\'); put('t'); }
|
|
else if (*i >= 0 && *i < 32) {
|
|
const char hex[17] = "0123456789abcdef";
|
|
put('\\');
|
|
put('u');
|
|
put(hex[(uint16_t(*i) >> 12) & 0xf]);
|
|
put(hex[(uint16_t(*i) >> 8) & 0xf]);
|
|
put(hex[(uint16_t(*i) >> 4) & 0xf]);
|
|
put(hex[(uint16_t(*i) >> 0) & 0xf]);
|
|
}
|
|
else put(*i);
|
|
}
|
|
put('"');
|
|
flush();
|
|
}
|
|
|
|
void toJSON(std::ostream & str, const char * s)
|
|
{
|
|
if (!s) str << "null"; else toJSON(str, s, s + strlen(s));
|
|
}
|
|
|
|
template<> void toJSON<int>(std::ostream & str, const int & n) { str << n; }
|
|
template<> void toJSON<unsigned int>(std::ostream & str, const unsigned int & n) { str << n; }
|
|
template<> void toJSON<long>(std::ostream & str, const long & n) { str << n; }
|
|
template<> void toJSON<unsigned long>(std::ostream & str, const unsigned long & n) { str << n; }
|
|
template<> void toJSON<long long>(std::ostream & str, const long long & n) { str << n; }
|
|
template<> void toJSON<unsigned long long>(std::ostream & str, const unsigned long long & n) { str << n; }
|
|
template<> void toJSON<float>(std::ostream & str, const float & n) { str << n; }
|
|
template<> void toJSON<double>(std::ostream & str, const double & n) { str << n; }
|
|
|
|
template<> void toJSON<std::string>(std::ostream & str, const std::string & s)
|
|
{
|
|
toJSON(str, s.c_str(), s.c_str() + s.size());
|
|
}
|
|
|
|
template<> void toJSON<bool>(std::ostream & str, const bool & b)
|
|
{
|
|
str << (b ? "true" : "false");
|
|
}
|
|
|
|
template<> void toJSON<std::nullptr_t>(std::ostream & str, const std::nullptr_t & b)
|
|
{
|
|
str << "null";
|
|
}
|
|
|
|
JSONWriter::JSONWriter(std::ostream & str, bool indent)
|
|
: state(new JSONState(str, indent))
|
|
{
|
|
state->stack++;
|
|
}
|
|
|
|
JSONWriter::JSONWriter(JSONState * state)
|
|
: state(state)
|
|
{
|
|
state->stack++;
|
|
}
|
|
|
|
JSONWriter::~JSONWriter()
|
|
{
|
|
if (state) {
|
|
assertActive();
|
|
state->stack--;
|
|
if (state->stack == 0) delete state;
|
|
}
|
|
}
|
|
|
|
void JSONWriter::comma()
|
|
{
|
|
assertActive();
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
state->str << ',';
|
|
}
|
|
if (state->indent) indent();
|
|
}
|
|
|
|
void JSONWriter::indent()
|
|
{
|
|
state->str << '\n' << std::string(state->depth * 2, ' ');
|
|
}
|
|
|
|
void JSONList::open()
|
|
{
|
|
state->depth++;
|
|
state->str << '[';
|
|
}
|
|
|
|
JSONList::~JSONList()
|
|
{
|
|
state->depth--;
|
|
if (state->indent && !first) indent();
|
|
state->str << "]";
|
|
}
|
|
|
|
JSONList JSONList::list()
|
|
{
|
|
comma();
|
|
return JSONList(state);
|
|
}
|
|
|
|
JSONObject JSONList::object()
|
|
{
|
|
comma();
|
|
return JSONObject(state);
|
|
}
|
|
|
|
JSONPlaceholder JSONList::placeholder()
|
|
{
|
|
comma();
|
|
return JSONPlaceholder(state);
|
|
}
|
|
|
|
void JSONObject::open()
|
|
{
|
|
state->depth++;
|
|
state->str << '{';
|
|
}
|
|
|
|
JSONObject::~JSONObject()
|
|
{
|
|
if (state) {
|
|
state->depth--;
|
|
if (state->indent && !first) indent();
|
|
state->str << "}";
|
|
}
|
|
}
|
|
|
|
void JSONObject::attr(const std::string & s)
|
|
{
|
|
comma();
|
|
toJSON(state->str, s);
|
|
state->str << ':';
|
|
if (state->indent) state->str << ' ';
|
|
}
|
|
|
|
JSONList JSONObject::list(const std::string & name)
|
|
{
|
|
attr(name);
|
|
return JSONList(state);
|
|
}
|
|
|
|
JSONObject JSONObject::object(const std::string & name)
|
|
{
|
|
attr(name);
|
|
return JSONObject(state);
|
|
}
|
|
|
|
JSONPlaceholder JSONObject::placeholder(const std::string & name)
|
|
{
|
|
attr(name);
|
|
return JSONPlaceholder(state);
|
|
}
|
|
|
|
JSONList JSONPlaceholder::list()
|
|
{
|
|
assertValid();
|
|
first = false;
|
|
return JSONList(state);
|
|
}
|
|
|
|
JSONObject JSONPlaceholder::object()
|
|
{
|
|
assertValid();
|
|
first = false;
|
|
return JSONObject(state);
|
|
}
|
|
|
|
JSONPlaceholder::~JSONPlaceholder()
|
|
{
|
|
assert(!first || std::uncaught_exceptions());
|
|
}
|
|
|
|
}
|