Skip to content

Commit a7edcfb

Browse files
committed
[Support] Add JSON streaming output API, faster where the heavy value types aren't needed.
Summary: There's still a little bit of constant factor that could be trimmed (e.g. more overloads to avoid round-tripping primitives through json::Value). But this solves the memory scaling problem, and greatly improves the performance constant factor, and the API should leave room for optimization if needed. Adapt TimeProfiler to use it, eliminating almost all the performance regression from r358476. Performance test on my machine: perf stat -r 5 ~/llvmbuild-opt/bin/clang++ -w -S -ftime-trace -mllvm -time-trace-granularity=0 spirit.cpp Handcrafted JSON (HEAD=r358532 with r358476 reverted): 2480ms json::Value (HEAD): 2757ms (+11%) After this patch: 2520 ms (+1.6%) Reviewers: anton-afanasyev, lebedev.ri Subscribers: kristina, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D60804 llvm-svn: 359186
1 parent 86ff9d3 commit a7edcfb

File tree

4 files changed

+337
-138
lines changed

4 files changed

+337
-138
lines changed

llvm/include/llvm/Support/JSON.h

Lines changed: 150 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
/// - a convention and helpers for mapping between json::Value and user-defined
2222
/// types. See fromJSON(), ObjectMapper, and the class comment on Value.
2323
///
24+
/// - an output API json::OStream which can emit JSON without materializing
25+
/// all structures as json::Value.
26+
///
2427
/// Typically, JSON data would be read from an external source, parsed into
2528
/// a Value, and then converted into some native data structure before doing
2629
/// real work on it. (And vice versa when writing).
@@ -437,11 +440,6 @@ class Value {
437440
return LLVM_LIKELY(Type == T_Array) ? &as<json::Array>() : nullptr;
438441
}
439442

440-
/// Serializes this Value to JSON, writing it to the provided stream.
441-
/// The formatting is compact (no extra whitespace) and deterministic.
442-
/// For pretty-printing, use the formatv() format_provider below.
443-
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Value &);
444-
445443
private:
446444
void destroy();
447445
void copyFrom(const Value &M);
@@ -462,9 +460,7 @@ class Value {
462460
return *static_cast<T *>(Storage);
463461
}
464462

465-
template <typename Indenter>
466-
void print(llvm::raw_ostream &, const Indenter &) const;
467-
friend struct llvm::format_provider<llvm::json::Value>;
463+
friend class OStream;
468464

469465
enum ValueType : char {
470466
T_Null,
@@ -486,7 +482,6 @@ class Value {
486482

487483
bool operator==(const Value &, const Value &);
488484
inline bool operator!=(const Value &L, const Value &R) { return !(L == R); }
489-
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Value &);
490485

491486
/// ObjectKey is a used to capture keys in Object. Like Value but:
492487
/// - only strings are allowed
@@ -699,6 +694,152 @@ class ParseError : public llvm::ErrorInfo<ParseError> {
699694
return llvm::inconvertibleErrorCode();
700695
}
701696
};
697+
698+
/// json::OStream allows writing well-formed JSON without materializing
699+
/// all structures as json::Value ahead of time.
700+
/// It's faster, lower-level, and less safe than OS << json::Value.
701+
///
702+
/// Only one "top-level" object can be written to a stream.
703+
/// Simplest usage involves passing lambdas (Blocks) to fill in containers:
704+
///
705+
/// json::OStream J(OS);
706+
/// J.array([&]{
707+
/// for (const Event &E : Events)
708+
/// J.object([&] {
709+
/// J.attribute("timestamp", int64_t(E.Time));
710+
/// J.attributeArray("participants", [&] {
711+
/// for (const Participant &P : E.Participants)
712+
/// J.string(P.toString());
713+
/// });
714+
/// });
715+
/// });
716+
///
717+
/// This would produce JSON like:
718+
///
719+
/// [
720+
/// {
721+
/// "timestamp": 19287398741,
722+
/// "participants": [
723+
/// "King Kong",
724+
/// "Miley Cyrus",
725+
/// "Cleopatra"
726+
/// ]
727+
/// },
728+
/// ...
729+
/// ]
730+
///
731+
/// The lower level begin/end methods (arrayBegin()) are more flexible but
732+
/// care must be taken to pair them correctly:
733+
///
734+
/// json::OStream J(OS);
735+
// J.arrayBegin();
736+
/// for (const Event &E : Events) {
737+
/// J.objectBegin();
738+
/// J.attribute("timestamp", int64_t(E.Time));
739+
/// J.attributeBegin("participants");
740+
/// for (const Participant &P : E.Participants)
741+
/// J.value(P.toString());
742+
/// J.attributeEnd();
743+
/// J.objectEnd();
744+
/// }
745+
/// J.arrayEnd();
746+
///
747+
/// If the call sequence isn't valid JSON, asserts will fire in debug mode.
748+
/// This can be mismatched begin()/end() pairs, trying to emit attributes inside
749+
/// an array, and so on.
750+
/// With asserts disabled, this is undefined behavior.
751+
class OStream {
752+
public:
753+
using Block = llvm::function_ref<void()>;
754+
// OStream does not buffer internally, and need never be flushed or destroyed.
755+
// If IndentSize is nonzero, output is pretty-printed.
756+
explicit OStream(llvm::raw_ostream &OS, unsigned IndentSize = 0)
757+
: OS(OS), IndentSize(IndentSize) {
758+
Stack.emplace_back();
759+
}
760+
~OStream() {
761+
assert(Stack.size() == 1 && "Unmatched begin()/end()");
762+
assert(Stack.back().Ctx == Singleton);
763+
assert(Stack.back().HasValue && "Did not write top-level value");
764+
}
765+
766+
// High level functions to output a value.
767+
// Valid at top-level (exactly once), in an attribute value (exactly once),
768+
// or in an array (any number of times).
769+
770+
/// Emit a self-contained value (number, string, vector<string> etc).
771+
void value(const Value &V);
772+
/// Emit an array whose elements are emitted in the provided Block.
773+
void array(Block Contents) {
774+
arrayBegin();
775+
Contents();
776+
arrayEnd();
777+
}
778+
/// Emit an object whose elements are emitted in the provided Block.
779+
void object(Block Contents) {
780+
objectBegin();
781+
Contents();
782+
objectEnd();
783+
}
784+
785+
// High level functions to output object attributes.
786+
// Valid only within an object (any number of times).
787+
788+
/// Emit an attribute whose value is self-contained (number, vector<int> etc).
789+
void attribute(llvm::StringRef Key, const Value& Contents) {
790+
attributeImpl(Key, [&] { value(Contents); });
791+
}
792+
/// Emit an attribute whose value is an array with elements from the Block.
793+
void attributeArray(llvm::StringRef Key, Block Contents) {
794+
attributeImpl(Key, [&] { array(Contents); });
795+
}
796+
/// Emit an attribute whose value is an object with attributes from the Block.
797+
void attributeObject(llvm::StringRef Key, Block Contents) {
798+
attributeImpl(Key, [&] { object(Contents); });
799+
}
800+
801+
// Low-level begin/end functions to output arrays, objects, and attributes.
802+
// Must be correctly paired. Allowed contexts are as above.
803+
804+
void arrayBegin();
805+
void arrayEnd();
806+
void objectBegin();
807+
void objectEnd();
808+
void attributeBegin(llvm::StringRef Key);
809+
void attributeEnd();
810+
811+
private:
812+
void attributeImpl(llvm::StringRef Key, Block Contents) {
813+
attributeBegin(Key);
814+
Contents();
815+
attributeEnd();
816+
}
817+
818+
void valueBegin();
819+
void newline();
820+
821+
enum Context {
822+
Singleton, // Top level, or object attribute.
823+
Array,
824+
Object,
825+
};
826+
struct State {
827+
Context Ctx = Singleton;
828+
bool HasValue = false;
829+
};
830+
llvm::SmallVector<State, 16> Stack; // Never empty.
831+
llvm::raw_ostream &OS;
832+
unsigned IndentSize;
833+
unsigned Indent = 0;
834+
};
835+
836+
/// Serializes this Value to JSON, writing it to the provided stream.
837+
/// The formatting is compact (no extra whitespace) and deterministic.
838+
/// For pretty-printing, use the formatv() format_provider below.
839+
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Value &V) {
840+
OStream(OS).value(V);
841+
return OS;
842+
}
702843
} // namespace json
703844

704845
/// Allow printing json::Value with formatv().

0 commit comments

Comments
 (0)