llvm.org GIT mirror llvm / 2802669
[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 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@359186 91177308-0d34-0410-b5e6-96231b3b80d8 Sam McCall 1 year, 7 months ago
4 changed file(s) with 343 addition(s) and 144 deletion(s). Raw diff Collapse all Expand all
1919 ///
2020 /// - a convention and helpers for mapping between json::Value and user-defined
2121 /// types. See fromJSON(), ObjectMapper, and the class comment on Value.
22 ///
23 /// - an output API json::OStream which can emit JSON without materializing
24 /// all structures as json::Value.
2225 ///
2326 /// Typically, JSON data would be read from an external source, parsed into
2427 /// a Value, and then converted into some native data structure before doing
436439 return LLVM_LIKELY(Type == T_Array) ? &as() : nullptr;
437440 }
438441
439 /// Serializes this Value to JSON, writing it to the provided stream.
440 /// The formatting is compact (no extra whitespace) and deterministic.
441 /// For pretty-printing, use the formatv() format_provider below.
442 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Value &);
443
444442 private:
445443 void destroy();
446444 void copyFrom(const Value &M);
461459 return *static_cast(Storage);
462460 }
463461
464 template
465 void print(llvm::raw_ostream &, const Indenter &) const;
466 friend struct llvm::format_provider;
462 friend class OStream;
467463
468464 enum ValueType : char {
469465 T_Null,
485481
486482 bool operator==(const Value &, const Value &);
487483 inline bool operator!=(const Value &L, const Value &R) { return !(L == R); }
488 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Value &);
489484
490485 /// ObjectKey is a used to capture keys in Object. Like Value but:
491486 /// - only strings are allowed
698693 return llvm::inconvertibleErrorCode();
699694 }
700695 };
696
697 /// json::OStream allows writing well-formed JSON without materializing
698 /// all structures as json::Value ahead of time.
699 /// It's faster, lower-level, and less safe than OS << json::Value.
700 ///
701 /// Only one "top-level" object can be written to a stream.
702 /// Simplest usage involves passing lambdas (Blocks) to fill in containers:
703 ///
704 /// json::OStream J(OS);
705 /// J.array([&]{
706 /// for (const Event &E : Events)
707 /// J.object([&] {
708 /// J.attribute("timestamp", int64_t(E.Time));
709 /// J.attributeArray("participants", [&] {
710 /// for (const Participant &P : E.Participants)
711 /// J.string(P.toString());
712 /// });
713 /// });
714 /// });
715 ///
716 /// This would produce JSON like:
717 ///
718 /// [
719 /// {
720 /// "timestamp": 19287398741,
721 /// "participants": [
722 /// "King Kong",
723 /// "Miley Cyrus",
724 /// "Cleopatra"
725 /// ]
726 /// },
727 /// ...
728 /// ]
729 ///
730 /// The lower level begin/end methods (arrayBegin()) are more flexible but
731 /// care must be taken to pair them correctly:
732 ///
733 /// json::OStream J(OS);
734 // J.arrayBegin();
735 /// for (const Event &E : Events) {
736 /// J.objectBegin();
737 /// J.attribute("timestamp", int64_t(E.Time));
738 /// J.attributeBegin("participants");
739 /// for (const Participant &P : E.Participants)
740 /// J.value(P.toString());
741 /// J.attributeEnd();
742 /// J.objectEnd();
743 /// }
744 /// J.arrayEnd();
745 ///
746 /// If the call sequence isn't valid JSON, asserts will fire in debug mode.
747 /// This can be mismatched begin()/end() pairs, trying to emit attributes inside
748 /// an array, and so on.
749 /// With asserts disabled, this is undefined behavior.
750 class OStream {
751 public:
752 using Block = llvm::function_ref;
753 // OStream does not buffer internally, and need never be flushed or destroyed.
754 // If IndentSize is nonzero, output is pretty-printed.
755 explicit OStream(llvm::raw_ostream &OS, unsigned IndentSize = 0)
756 : OS(OS), IndentSize(IndentSize) {
757 Stack.emplace_back();
758 }
759 ~OStream() {
760 assert(Stack.size() == 1 && "Unmatched begin()/end()");
761 assert(Stack.back().Ctx == Singleton);
762 assert(Stack.back().HasValue && "Did not write top-level value");
763 }
764
765 // High level functions to output a value.
766 // Valid at top-level (exactly once), in an attribute value (exactly once),
767 // or in an array (any number of times).
768
769 /// Emit a self-contained value (number, string, vector etc).
770 void value(const Value &V);
771 /// Emit an array whose elements are emitted in the provided Block.
772 void array(Block Contents) {
773 arrayBegin();
774 Contents();
775 arrayEnd();
776 }
777 /// Emit an object whose elements are emitted in the provided Block.
778 void object(Block Contents) {
779 objectBegin();
780 Contents();
781 objectEnd();
782 }
783
784 // High level functions to output object attributes.
785 // Valid only within an object (any number of times).
786
787 /// Emit an attribute whose value is self-contained (number, vector etc).
788 void attribute(llvm::StringRef Key, const Value& Contents) {
789 attributeImpl(Key, [&] { value(Contents); });
790 }
791 /// Emit an attribute whose value is an array with elements from the Block.
792 void attributeArray(llvm::StringRef Key, Block Contents) {
793 attributeImpl(Key, [&] { array(Contents); });
794 }
795 /// Emit an attribute whose value is an object with attributes from the Block.
796 void attributeObject(llvm::StringRef Key, Block Contents) {
797 attributeImpl(Key, [&] { object(Contents); });
798 }
799
800 // Low-level begin/end functions to output arrays, objects, and attributes.
801 // Must be correctly paired. Allowed contexts are as above.
802
803 void arrayBegin();
804 void arrayEnd();
805 void objectBegin();
806 void objectEnd();
807 void attributeBegin(llvm::StringRef Key);
808 void attributeEnd();
809
810 private:
811 void attributeImpl(llvm::StringRef Key, Block Contents) {
812 attributeBegin(Key);
813 Contents();
814 attributeEnd();
815 }
816
817 void valueBegin();
818 void newline();
819
820 enum Context {
821 Singleton, // Top level, or object attribute.
822 Array,
823 Object,
824 };
825 struct State {
826 Context Ctx = Singleton;
827 bool HasValue = false;
828 };
829 llvm::SmallVector Stack; // Never empty.
830 llvm::raw_ostream &OS;
831 unsigned IndentSize;
832 unsigned Indent = 0;
833 };
834
835 /// Serializes this Value to JSON, writing it to the provided stream.
836 /// The formatting is compact (no extra whitespace) and deterministic.
837 /// For pretty-printing, use the formatv() format_provider below.
838 inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Value &V) {
839 OStream(OS).value(V);
840 return OS;
841 }
701842 } // namespace json
702843
703844 /// Allow printing json::Value with formatv().
559559 return Res;
560560 }
561561
562 } // namespace json
563 } // namespace llvm
564
565562 static void quote(llvm::raw_ostream &OS, llvm::StringRef S) {
566563 OS << '\"';
567564 for (unsigned char C : S) {
592589 OS << '\"';
593590 }
594591
595 enum IndenterAction {
596 Indent,
597 Outdent,
598 Newline,
599 Space,
600 };
601
602 // Prints JSON. The indenter can be used to control formatting.
603 template
604 void llvm::json::Value::print(raw_ostream &OS, const Indenter &I) const {
605 switch (Type) {
606 case T_Null:
592 void llvm::json::OStream::value(const Value &V) {
593 switch (V.kind()) {
594 case Value::Null:
595 valueBegin();
607596 OS << "null";
608 break;
609 case T_Boolean:
610 OS << (as() ? "true" : "false");
611 break;
612 case T_Double:
613 OS << format("%.*g", std::numeric_limits::max_digits10,
614 as());
615 break;
616 case T_Integer:
617 OS << as();
618 break;
619 case T_StringRef:
620 quote(OS, as());
621 break;
622 case T_String:
623 quote(OS, as());
624 break;
625 case T_Object: {
626 bool Comma = false;
627 OS << '{';
628 I(Indent);
629 for (const auto *P : sortedElements(as())) {
630 if (Comma)
631 OS << ',';
632 Comma = true;
633 I(Newline);
634 quote(OS, P->first);
635 OS << ':';
636 I(Space);
637 P->second.print(OS, I);
638 }
639 I(Outdent);
640 if (Comma)
641 I(Newline);
642 OS << '}';
643 break;
644 }
645 case T_Array: {
646 bool Comma = false;
647 OS << '[';
648 I(Indent);
649 for (const auto &E : as()) {
650 if (Comma)
651 OS << ',';
652 Comma = true;
653 I(Newline);
654 E.print(OS, I);
655 }
656 I(Outdent);
657 if (Comma)
658 I(Newline);
659 OS << ']';
660 break;
661 }
662 }
663 }
597 return;
598 case Value::Boolean:
599 valueBegin();
600 OS << (*V.getAsBoolean() ? "true" : "false");
601 return;
602 case Value::Number:
603 valueBegin();
604 if (V.Type == Value::T_Integer)
605 OS << *V.getAsInteger();
606 else
607 OS << format("%.*g", std::numeric_limits::max_digits10,
608 *V.getAsNumber());
609 return;
610 case Value::String:
611 valueBegin();
612 quote(OS, *V.getAsString());
613 return;
614 case Value::Array:
615 return array([&] {
616 for (const Value &E : *V.getAsArray())
617 value(E);
618 });
619 case Value::Object:
620 return object([&] {
621 for (const Object::value_type *E : sortedElements(*V.getAsObject()))
622 attribute(E->first, E->second);
623 });
624 }
625 }
626
627 void llvm::json::OStream::valueBegin() {
628 assert(Stack.back().Ctx != Object && "Only attributes allowed here");
629 if (Stack.back().HasValue) {
630 assert(Stack.back().Ctx != Singleton && "Only one value allowed here");
631 OS << ',';
632 }
633 if (Stack.back().Ctx == Array)
634 newline();
635 Stack.back().HasValue = true;
636 }
637
638 void llvm::json::OStream::newline() {
639 if (IndentSize) {
640 OS.write('\n');
641 OS.indent(Indent);
642 }
643 }
644
645 void llvm::json::OStream::arrayBegin() {
646 valueBegin();
647 Stack.emplace_back();
648 Stack.back().Ctx = Array;
649 Indent += IndentSize;
650 OS << '[';
651 }
652
653 void llvm::json::OStream::arrayEnd() {
654 assert(Stack.back().Ctx == Array);
655 Indent -= IndentSize;
656 if (Stack.back().HasValue)
657 newline();
658 OS << ']';
659 Stack.pop_back();
660 assert(!Stack.empty());
661 }
662
663 void llvm::json::OStream::objectBegin() {
664 valueBegin();
665 Stack.emplace_back();
666 Stack.back().Ctx = Object;
667 Indent += IndentSize;
668 OS << '{';
669 }
670
671 void llvm::json::OStream::objectEnd() {
672 assert(Stack.back().Ctx == Object);
673 Indent -= IndentSize;
674 if (Stack.back().HasValue)
675 newline();
676 OS << '}';
677 Stack.pop_back();
678 assert(!Stack.empty());
679 }
680
681 void llvm::json::OStream::attributeBegin(llvm::StringRef Key) {
682 assert(Stack.back().Ctx == Object);
683 if (Stack.back().HasValue)
684 OS << ',';
685 newline();
686 Stack.back().HasValue = true;
687 Stack.emplace_back();
688 Stack.back().Ctx = Singleton;
689 if (LLVM_LIKELY(isUTF8(Key))) {
690 quote(OS, Key);
691 } else {
692 assert(false && "Invalid UTF-8 in attribute key");
693 quote(OS, fixUTF8(Key));
694 }
695 OS.write(':');
696 if (IndentSize)
697 OS.write(' ');
698 }
699
700 void llvm::json::OStream::attributeEnd() {
701 assert(Stack.back().Ctx == Singleton);
702 assert(Stack.back().HasValue && "Attribute must have a value");
703 Stack.pop_back();
704 assert(Stack.back().Ctx == Object);
705 }
706
707 } // namespace json
708 } // namespace llvm
664709
665710 void llvm::format_provider::format(
666711 const llvm::json::Value &E, raw_ostream &OS, StringRef Options) {
667 if (Options.empty()) {
668 OS << E;
669 return;
670 }
671712 unsigned IndentAmount = 0;
672 if (Options.getAsInteger(/*Radix=*/10, IndentAmount))
713 if (!Options.empty() && Options.getAsInteger(/*Radix=*/10, IndentAmount))
673714 llvm_unreachable("json::Value format options should be an integer");
674 unsigned IndentLevel = 0;
675 E.print(OS, [&](IndenterAction A) {
676 switch (A) {
677 case Newline:
678 OS << '\n';
679 OS.indent(IndentLevel);
680 break;
681 case Space:
682 OS << ' ';
683 break;
684 case Indent:
685 IndentLevel += IndentAmount;
686 break;
687 case Outdent:
688 IndentLevel -= IndentAmount;
689 break;
690 };
691 });
692 }
693
694 llvm::raw_ostream &llvm::json::operator<<(raw_ostream &OS, const Value &E) {
695 E.print(OS, [](IndenterAction A) { /*ignore*/ });
696 return OS;
697 }
715 json::OStream(OS, IndentAmount).value(E);
716 }
717
8686 void Write(raw_pwrite_stream &OS) {
8787 assert(Stack.empty() &&
8888 "All profiler sections should be ended when calling Write");
89
90 json::Array Events;
91 const size_t ExpectedEntryCount =
92 Entries.size() + CountAndTotalPerName.size() + 1;
93 Events.reserve(ExpectedEntryCount);
89 json::OStream J(OS);
90 J.objectBegin();
91 J.attributeBegin("traceEvents");
92 J.arrayBegin();
9493
9594 // Emit all events for the main flame graph.
9695 for (const auto &E : Entries) {
9796 auto StartUs = duration_cast(E.Start - StartTime).count();
9897 auto DurUs = duration_cast(E.Duration).count();
9998
100 Events.emplace_back(json::Object{
101 {"pid", 1},
102 {"tid", 0},
103 {"ph", "X"},
104 {"ts", StartUs},
105 {"dur", DurUs},
106 {"name", E.Name},
107 {"args", json::Object{{"detail", E.Detail}}},
99 J.object([&]{
100 J.attribute("pid", 1);
101 J.attribute("tid", 0);
102 J.attribute("ph", "X");
103 J.attribute("ts", StartUs);
104 J.attribute("dur", DurUs);
105 J.attribute("name", E.Name);
106 J.attributeObject("args", [&] { J.attribute("detail", E.Detail); });
108107 });
109108 }
110109
125124 auto DurUs = duration_cast(E.second.second).count();
126125 auto Count = CountAndTotalPerName[E.first].first;
127126
128 Events.emplace_back(json::Object{
129 {"pid", 1},
130 {"tid", Tid},
131 {"ph", "X"},
132 {"ts", 0},
133 {"dur", DurUs},
134 {"name", "Total " + E.first},
135 {"args", json::Object{{"count", static_cast(Count)},
136 {"avg ms",
137 static_cast(DurUs / Count / 1000)}}},
127 J.object([&]{
128 J.attribute("pid", 1);
129 J.attribute("tid", Tid);
130 J.attribute("ph", "X");
131 J.attribute("ts", 0);
132 J.attribute("dur", DurUs);
133 J.attribute("name", "Total " + E.first);
134 J.attributeObject("args", [&] {
135 J.attribute("count", int64_t(Count));
136 J.attribute("avg ms", int64_t(DurUs / Count / 1000));
137 });
138138 });
139139
140140 ++Tid;
141141 }
142142
143143 // Emit metadata event with process name.
144 Events.emplace_back(json::Object{
145 {"cat", ""},
146 {"pid", 1},
147 {"tid", 0},
148 {"ts", 0},
149 {"ph", "M"},
150 {"name", "process_name"},
151 {"args", json::Object{{"name", "clang"}}},
144 J.object([&] {
145 J.attribute("cat", "");
146 J.attribute("pid", 1);
147 J.attribute("tid", 0);
148 J.attribute("ts", 0);
149 J.attribute("ph", "M");
150 J.attribute("name", "process_name");
151 J.attributeObject("args", [&] { J.attribute("name", "clang"); });
152152 });
153153
154 assert(Events.size() == ExpectedEntryCount && "Size prediction failed!");
155
156 OS << formatv("{0:2}", json::Value(json::Object(
157 {{"traceEvents", std::move(Events)}})));
154 J.arrayEnd();
155 J.attributeEnd();
156 J.objectEnd();
158157 }
159158
160159 SmallVector Stack;
66 //===----------------------------------------------------------------------===//
77
88 #include "llvm/Support/JSON.h"
9 #include "llvm/Support/raw_ostream.h"
910
1011 #include "gmock/gmock.h"
1112 #include "gtest/gtest.h"
382383 << "Wrong type for Optional " << V;
383384 }
384385
386 TEST(JSONTest, Stream) {
387 auto StreamStuff = [](unsigned Indent) {
388 std::string S;
389 llvm::raw_string_ostream OS(S);
390 OStream J(OS, Indent);
391 J.object([&] {
392 J.attributeArray("foo", [&] {
393 J.value(nullptr);
394 J.value(42.5);
395 J.arrayBegin();
396 J.value(43);
397 J.arrayEnd();
398 });
399 J.attributeBegin("bar");
400 J.objectBegin();
401 J.objectEnd();
402 J.attributeEnd();
403 J.attribute("baz", "xyz");
404 });
405 return OS.str();
406 };
407
408 const char *Plain = R"({"foo":[null,42.5,[43]],"bar":{},"baz":"xyz"})";
409 EXPECT_EQ(Plain, StreamStuff(0));
410 const char *Pretty = R"({
411 "foo": [
412 null,
413 42.5,
414 [
415 43
416 ]
417 ],
418 "bar": {},
419 "baz": "xyz"
420 })";
421 EXPECT_EQ(Pretty, StreamStuff(2));
422 }
423
385424 } // namespace
386425 } // namespace json
387426 } // namespace llvm